summaryrefslogtreecommitdiffstats
path: root/src/st
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/st/croco/cr-additional-sel.c498
-rw-r--r--src/st/croco/cr-additional-sel.h98
-rw-r--r--src/st/croco/cr-attr-sel.c234
-rw-r--r--src/st/croco/cr-attr-sel.h74
-rw-r--r--src/st/croco/cr-cascade.c215
-rw-r--r--src/st/croco/cr-cascade.h74
-rw-r--r--src/st/croco/cr-declaration.c798
-rw-r--r--src/st/croco/cr-declaration.h136
-rw-r--r--src/st/croco/cr-doc-handler.c276
-rw-r--r--src/st/croco/cr-doc-handler.h298
-rw-r--r--src/st/croco/cr-enc-handler.c184
-rw-r--r--src/st/croco/cr-enc-handler.h94
-rw-r--r--src/st/croco/cr-fonts.c948
-rw-r--r--src/st/croco/cr-fonts.h315
-rw-r--r--src/st/croco/cr-input.c1191
-rw-r--r--src/st/croco/cr-input.h174
-rw-r--r--src/st/croco/cr-num.c313
-rw-r--r--src/st/croco/cr-num.h127
-rw-r--r--src/st/croco/cr-om-parser.c1142
-rw-r--r--src/st/croco/cr-om-parser.h98
-rw-r--r--src/st/croco/cr-parser.c4539
-rw-r--r--src/st/croco/cr-parser.h128
-rw-r--r--src/st/croco/cr-parsing-location.c171
-rw-r--r--src/st/croco/cr-parsing-location.h70
-rw-r--r--src/st/croco/cr-prop-list.c404
-rw-r--r--src/st/croco/cr-prop-list.h80
-rw-r--r--src/st/croco/cr-pseudo.c166
-rw-r--r--src/st/croco/cr-pseudo.h64
-rw-r--r--src/st/croco/cr-rgb.c604
-rw-r--r--src/st/croco/cr-rgb.h84
-rw-r--r--src/st/croco/cr-selector.c305
-rw-r--r--src/st/croco/cr-selector.h95
-rw-r--r--src/st/croco/cr-simple-sel.c323
-rw-r--r--src/st/croco/cr-simple-sel.h130
-rw-r--r--src/st/croco/cr-statement.c2784
-rw-r--r--src/st/croco/cr-statement.h440
-rw-r--r--src/st/croco/cr-string.c168
-rw-r--r--src/st/croco/cr-string.h76
-rw-r--r--src/st/croco/cr-stylesheet.c177
-rw-r--r--src/st/croco/cr-stylesheet.h102
-rw-r--r--src/st/croco/cr-term.c786
-rw-r--r--src/st/croco/cr-term.h190
-rw-r--r--src/st/croco/cr-tknzr.c2762
-rw-r--r--src/st/croco/cr-tknzr.h115
-rw-r--r--src/st/croco/cr-token.c636
-rw-r--r--src/st/croco/cr-token.h212
-rw-r--r--src/st/croco/cr-utils.c1330
-rw-r--r--src/st/croco/cr-utils.h246
-rw-r--r--src/st/croco/libcroco-config.h13
-rw-r--r--src/st/croco/libcroco.h42
-rw-r--r--src/st/meson.build220
-rw-r--r--src/st/st-adjustment.c1013
-rw-r--r--src/st/st-adjustment.h92
-rw-r--r--src/st/st-bin.c404
-rw-r--r--src/st/st-bin.h53
-rw-r--r--src/st/st-border-image.c171
-rw-r--r--src/st/st-border-image.h54
-rw-r--r--src/st/st-box-layout.c307
-rw-r--r--src/st/st-box-layout.h65
-rw-r--r--src/st/st-button.c1043
-rw-r--r--src/st/st-button.h85
-rw-r--r--src/st/st-clipboard.c348
-rw-r--r--src/st/st-clipboard.h105
-rw-r--r--src/st/st-drawing-area.c242
-rw-r--r--src/st/st-drawing-area.h44
-rw-r--r--src/st/st-entry.c1626
-rw-r--r--src/st/st-entry.h79
-rw-r--r--src/st/st-focus-manager.c256
-rw-r--r--src/st/st-focus-manager.h65
-rw-r--r--src/st/st-generic-accessible.c246
-rw-r--r--src/st/st-generic-accessible.h62
-rw-r--r--src/st/st-icon-colors.c133
-rw-r--r--src/st/st-icon-colors.h43
-rw-r--r--src/st/st-icon.c833
-rw-r--r--src/st/st-icon.h82
-rw-r--r--src/st/st-image-content.c346
-rw-r--r--src/st/st-image-content.h33
-rw-r--r--src/st/st-label.c549
-rw-r--r--src/st/st-label.h58
-rw-r--r--src/st/st-password-entry.c363
-rw-r--r--src/st/st-password-entry.h46
-rw-r--r--src/st/st-private.c804
-rw-r--r--src/st/st-private.h75
-rw-r--r--src/st/st-scroll-bar.c1014
-rw-r--r--src/st/st-scroll-bar.h53
-rw-r--r--src/st/st-scroll-view-fade.c461
-rw-r--r--src/st/st-scroll-view-fade.glsl77
-rw-r--r--src/st/st-scroll-view-fade.h36
-rw-r--r--src/st/st-scroll-view.c1327
-rw-r--r--src/st/st-scroll-view.h90
-rw-r--r--src/st/st-scrollable.c196
-rw-r--r--src/st/st-scrollable.h59
-rw-r--r--src/st/st-settings.c451
-rw-r--r--src/st/st-settings.h42
-rw-r--r--src/st/st-shadow.c307
-rw-r--r--src/st/st-shadow.h95
-rw-r--r--src/st/st-texture-cache.c1688
-rw-r--r--src/st/st-texture-cache.h120
-rw-r--r--src/st/st-theme-context.c492
-rw-r--r--src/st/st-theme-context.h65
-rw-r--r--src/st/st-theme-node-drawing.c2864
-rw-r--r--src/st/st-theme-node-private.h131
-rw-r--r--src/st/st-theme-node-transition.c470
-rw-r--r--src/st/st-theme-node-transition.h58
-rw-r--r--src/st/st-theme-node.c4325
-rw-r--r--src/st/st-theme-node.h368
-rw-r--r--src/st/st-theme-private.h41
-rw-r--r--src/st/st-theme.c1085
-rw-r--r--src/st/st-theme.h51
-rw-r--r--src/st/st-types.h52
-rw-r--r--src/st/st-viewport.c600
-rw-r--r--src/st/st-viewport.h40
-rw-r--r--src/st/st-widget-accessible.h76
-rw-r--r--src/st/st-widget.c3049
-rw-r--r--src/st/st-widget.h167
-rw-r--r--src/st/st.h.in3
-rw-r--r--src/st/test-theme.c637
-rw-r--r--src/st/test-theme.css107
118 files changed, 55171 insertions, 0 deletions
diff --git a/src/st/croco/cr-additional-sel.c b/src/st/croco/cr-additional-sel.c
new file mode 100644
index 0000000..9bd8d6a
--- /dev/null
+++ b/src/st/croco/cr-additional-sel.c
@@ -0,0 +1,498 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ *
+ */
+
+#include "cr-additional-sel.h"
+#include "string.h"
+
+/**
+ * CRAdditionalSel:
+ *
+ * #CRAdditionalSel abstracts an additional selector.
+ * An additional selector is the selector part
+ * that comes after the combination of type selectors.
+ * It can be either "a class selector (the .class part),
+ * a pseudo class selector, an attribute selector
+ * or an id selector.
+ */
+
+/**
+ * cr_additional_sel_new:
+ *
+ * Default constructor of #CRAdditionalSel.
+ * Returns the newly build instance of #CRAdditionalSel.
+ */
+CRAdditionalSel *
+cr_additional_sel_new (void)
+{
+ CRAdditionalSel *result = NULL;
+
+ result = g_try_malloc (sizeof (CRAdditionalSel));
+
+ if (result == NULL) {
+ cr_utils_trace_debug ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRAdditionalSel));
+
+ return result;
+}
+
+/**
+ * cr_additional_sel_new_with_type:
+ * @a_sel_type: the type of the newly built instance
+ * of #CRAdditionalSel.
+ *
+ * Constructor of #CRAdditionalSel.
+ * Returns the newly built instance of #CRAdditionalSel.
+ */
+CRAdditionalSel *
+cr_additional_sel_new_with_type (enum AddSelectorType a_sel_type)
+{
+ CRAdditionalSel *result = NULL;
+
+ result = cr_additional_sel_new ();
+
+ g_return_val_if_fail (result, NULL);
+
+ result->type = a_sel_type;
+
+ return result;
+}
+
+/**
+ * cr_additional_sel_set_class_name:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_class_name: the new class name to set.
+ *
+ * Sets a new class name to a
+ * CLASS additional selector.
+ */
+void
+cr_additional_sel_set_class_name (CRAdditionalSel * a_this,
+ CRString * a_class_name)
+{
+ g_return_if_fail (a_this && a_this->type == CLASS_ADD_SELECTOR);
+
+ if (a_this->content.class_name) {
+ cr_string_destroy (a_this->content.class_name);
+ }
+
+ a_this->content.class_name = a_class_name;
+}
+
+/**
+ * cr_additional_sel_set_id_name:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_id: the new id to set.
+ *
+ * Sets a new id name to an
+ * ID additional selector.
+ */
+void
+cr_additional_sel_set_id_name (CRAdditionalSel * a_this, CRString * a_id)
+{
+ g_return_if_fail (a_this && a_this->type == ID_ADD_SELECTOR);
+
+ if (a_this->content.id_name) {
+ cr_string_destroy (a_this->content.id_name);
+ }
+
+ a_this->content.id_name = a_id;
+}
+
+/**
+ * cr_additional_sel_set_pseudo:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_pseudo: the new pseudo to set.
+ *
+ * Sets a new pseudo to a
+ * PSEUDO additional selector.
+ */
+void
+cr_additional_sel_set_pseudo (CRAdditionalSel * a_this, CRPseudo * a_pseudo)
+{
+ g_return_if_fail (a_this
+ && a_this->type == PSEUDO_CLASS_ADD_SELECTOR);
+
+ if (a_this->content.pseudo) {
+ cr_pseudo_destroy (a_this->content.pseudo);
+ }
+
+ a_this->content.pseudo = a_pseudo;
+}
+
+/**
+ * cr_additional_sel_set_attr_sel:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_sel: the new instance of #CRAttrSel to set.
+ *
+ * Sets a new instance of #CRAttrSel to
+ * a ATTRIBUTE additional selector.
+ */
+void
+cr_additional_sel_set_attr_sel (CRAdditionalSel * a_this, CRAttrSel * a_sel)
+{
+ g_return_if_fail (a_this && a_this->type == ATTRIBUTE_ADD_SELECTOR);
+
+ if (a_this->content.attr_sel) {
+ cr_attr_sel_destroy (a_this->content.attr_sel);
+ }
+
+ a_this->content.attr_sel = a_sel;
+}
+
+/**
+ * cr_additional_sel_append:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_sel: the new instance to #CRAdditional to append.
+ *
+ * Appends a new instance of #CRAdditional to the
+ * current list of #CRAdditional.
+ *
+ * Returns the new list of CRAdditionalSel or NULL if an error arises.
+ */
+CRAdditionalSel *
+cr_additional_sel_append (CRAdditionalSel * a_this, CRAdditionalSel * a_sel)
+{
+ CRAdditionalSel *cur_sel = NULL;
+
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL) {
+ return a_sel;
+ }
+
+ if (a_sel == NULL)
+ return NULL;
+
+ for (cur_sel = a_this;
+ cur_sel && cur_sel->next; cur_sel = cur_sel->next) ;
+
+ g_return_val_if_fail (cur_sel != NULL, NULL);
+
+ cur_sel->next = a_sel;
+ a_sel->prev = cur_sel;
+
+ return a_this;
+}
+
+/**
+ * cr_additional_sel_prepend:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_sel: the new instance to #CRAdditional to preappend.
+ *
+ * Preppends a new instance of #CRAdditional to the
+ * current list of #CRAdditional.
+ *
+ * Returns the new list of CRAdditionalSel or NULL if an error arises.
+ */
+CRAdditionalSel *
+cr_additional_sel_prepend (CRAdditionalSel * a_this, CRAdditionalSel * a_sel)
+{
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL) {
+ return a_sel;
+ }
+
+ a_sel->next = a_this;
+ a_this->prev = a_sel;
+
+ return a_sel;
+}
+
+guchar *
+cr_additional_sel_to_string (CRAdditionalSel const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+ CRAdditionalSel const *cur = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ switch (cur->type) {
+ case CLASS_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (cur->content.class_name) {
+ name = (guchar *) g_strndup
+ (cur->content.class_name->stryng->str,
+ cur->content.class_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, ".%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+ break;
+
+ case ID_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (cur->content.id_name) {
+ name = (guchar *) g_strndup
+ (cur->content.id_name->stryng->str,
+ cur->content.id_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, "#%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+
+ break;
+
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ {
+ if (cur->content.pseudo) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_pseudo_to_string
+ (cur->content.pseudo);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, ":%s",
+ tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+ break;
+
+ case ATTRIBUTE_ADD_SELECTOR:
+ if (cur->content.attr_sel) {
+ guchar *tmp_str = NULL;
+
+ g_string_append_c (str_buf, '[');
+ tmp_str = cr_attr_sel_to_string
+ (cur->content.attr_sel);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, "%s]", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+guchar *
+cr_additional_sel_one_to_string (CRAdditionalSel const *a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ str_buf = g_string_new (NULL) ;
+
+ switch (a_this->type) {
+ case CLASS_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (a_this->content.class_name) {
+ name = (guchar *) g_strndup
+ (a_this->content.class_name->stryng->str,
+ a_this->content.class_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, ".%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+ break;
+
+ case ID_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (a_this->content.id_name) {
+ name = (guchar *) g_strndup
+ (a_this->content.id_name->stryng->str,
+ a_this->content.id_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, "#%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+
+ break;
+
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ {
+ if (a_this->content.pseudo) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_pseudo_to_string
+ (a_this->content.pseudo);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, ":%s",
+ tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+ break;
+
+ case ATTRIBUTE_ADD_SELECTOR:
+ if (a_this->content.attr_sel) {
+ guchar *tmp_str = NULL;
+
+ g_string_append_printf (str_buf, "[");
+ tmp_str = cr_attr_sel_to_string
+ (a_this->content.attr_sel);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, "%s]", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_additional_sel_dump:
+ * @a_this: the "this pointer" of the current instance of
+ * #CRAdditionalSel.
+ * @a_fp: the destination file.
+ *
+ * Dumps the current instance of #CRAdditionalSel to a file
+ */
+void
+cr_additional_sel_dump (CRAdditionalSel const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ g_return_if_fail (a_fp);
+
+ if (a_this) {
+ tmp_str = cr_additional_sel_to_string (a_this);
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+}
+
+/**
+ * cr_additional_sel_destroy:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ *
+ * Destroys an instance of #CRAdditional.
+ */
+void
+cr_additional_sel_destroy (CRAdditionalSel * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case CLASS_ADD_SELECTOR:
+ cr_string_destroy (a_this->content.class_name);
+ a_this->content.class_name = NULL;
+ break;
+
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ cr_pseudo_destroy (a_this->content.pseudo);
+ a_this->content.pseudo = NULL;
+ break;
+
+ case ID_ADD_SELECTOR:
+ cr_string_destroy (a_this->content.id_name);
+ a_this->content.id_name = NULL;
+ break;
+
+ case ATTRIBUTE_ADD_SELECTOR:
+ cr_attr_sel_destroy (a_this->content.attr_sel);
+ a_this->content.attr_sel = NULL;
+ break;
+
+ default:
+ break;
+ }
+
+ if (a_this->next) {
+ cr_additional_sel_destroy (a_this->next);
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-additional-sel.h b/src/st/croco/cr-additional-sel.h
new file mode 100644
index 0000000..7ca3e07
--- /dev/null
+++ b/src/st/croco/cr-additional-sel.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+
+#ifndef __CR_ADD_SEL_H__
+#define __CR_ADD_SEL_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-attr-sel.h"
+#include "cr-pseudo.h"
+#include "cr-additional-sel.h"
+
+G_BEGIN_DECLS
+
+enum AddSelectorType
+{
+ NO_ADD_SELECTOR = 0 ,
+ CLASS_ADD_SELECTOR = 1 ,
+ PSEUDO_CLASS_ADD_SELECTOR = 1 << 1,
+ ID_ADD_SELECTOR = 1 << 3,
+ ATTRIBUTE_ADD_SELECTOR = 1 << 4
+} ;
+
+union CRAdditionalSelectorContent
+{
+ CRString *class_name ;
+ CRString *id_name ;
+ CRPseudo *pseudo ;
+ CRAttrSel *attr_sel ;
+} ;
+
+typedef struct _CRAdditionalSel CRAdditionalSel ;
+
+struct _CRAdditionalSel
+{
+ enum AddSelectorType type ;
+ union CRAdditionalSelectorContent content ;
+
+ CRAdditionalSel * next ;
+ CRAdditionalSel * prev ;
+ CRParsingLocation location ;
+} ;
+
+CRAdditionalSel * cr_additional_sel_new (void) ;
+
+CRAdditionalSel * cr_additional_sel_new_with_type (enum AddSelectorType a_sel_type) ;
+
+CRAdditionalSel * cr_additional_sel_append (CRAdditionalSel *a_this,
+ CRAdditionalSel *a_sel) ;
+
+void cr_additional_sel_set_class_name (CRAdditionalSel *a_this,
+ CRString *a_class_name) ;
+
+void cr_additional_sel_set_id_name (CRAdditionalSel *a_this,
+ CRString *a_id) ;
+
+void cr_additional_sel_set_pseudo (CRAdditionalSel *a_this,
+ CRPseudo *a_pseudo) ;
+
+void cr_additional_sel_set_attr_sel (CRAdditionalSel *a_this,
+ CRAttrSel *a_sel) ;
+
+CRAdditionalSel * cr_additional_sel_prepend (CRAdditionalSel *a_this,
+ CRAdditionalSel *a_sel) ;
+
+guchar * cr_additional_sel_to_string (CRAdditionalSel const *a_this) ;
+
+guchar * cr_additional_sel_one_to_string (CRAdditionalSel const *a_this) ;
+
+void cr_additional_sel_dump (CRAdditionalSel const *a_this, FILE *a_fp) ;
+
+void cr_additional_sel_destroy (CRAdditionalSel *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_ADD_SEL_H*/
diff --git a/src/st/croco/cr-attr-sel.c b/src/st/croco/cr-attr-sel.c
new file mode 100644
index 0000000..fc8e6ef
--- /dev/null
+++ b/src/st/croco/cr-attr-sel.c
@@ -0,0 +1,234 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#include <stdio.h>
+#include "cr-attr-sel.h"
+
+/**
+ * CRAttrSel:
+ *
+ * #CRAdditionalSel abstracts an attribute selector.
+ * Attributes selectors are described in the css2 spec [5.8].
+ * There are more generally used in the css2 selectors described in
+ * css2 spec [5] .
+ */
+
+/**
+ * cr_attr_sel_new:
+ * The constructor of #CRAttrSel.
+ * Returns the newly allocated instance
+ * of #CRAttrSel.
+ */
+CRAttrSel *
+cr_attr_sel_new (void)
+{
+ CRAttrSel *result = NULL;
+
+ result = g_malloc0 (sizeof (CRAttrSel));
+
+ return result;
+}
+
+/**
+ * cr_attr_sel_append_attr_sel:
+ * @a_this: the this pointer of the current instance of #CRAttrSel.
+ * @a_attr_sel: selector to append.
+ *
+ * Appends an attribute selector to the current list of
+ * attribute selectors represented by a_this.
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_attr_sel_append_attr_sel (CRAttrSel * a_this, CRAttrSel * a_attr_sel)
+{
+ CRAttrSel *cur_sel = NULL;
+
+ g_return_val_if_fail (a_this && a_attr_sel,
+ CR_BAD_PARAM_ERROR);
+
+ for (cur_sel = a_this;
+ cur_sel->next;
+ cur_sel = cur_sel->next) ;
+
+ cur_sel->next = a_attr_sel;
+ a_attr_sel->prev = cur_sel;
+
+ return CR_OK;
+}
+
+/**
+ * cr_attr_sel_prepend_attr_sel:
+ *@a_this: the "this pointer" of the current instance *of #CRAttrSel.
+ *@a_attr_sel: the attribute selector to append.
+ *
+ *Prepends an attribute selector to the list of
+ *attributes selector represented by a_this.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_attr_sel_prepend_attr_sel (CRAttrSel * a_this,
+ CRAttrSel * a_attr_sel)
+{
+ g_return_val_if_fail (a_this && a_attr_sel,
+ CR_BAD_PARAM_ERROR);
+
+ a_attr_sel->next = a_this;
+ a_this->prev = a_attr_sel;
+
+ return CR_OK;
+}
+
+/**
+ * cr_attr_sel_to_string:
+ * @a_this: the current instance of #CRAttrSel.
+ *
+ * Serializes an attribute selector into a string
+ * Returns the serialized attribute selector.
+ */
+guchar *
+cr_attr_sel_to_string (CRAttrSel const * a_this)
+{
+ CRAttrSel const *cur = NULL;
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->prev) {
+ g_string_append_c (str_buf, ' ');
+ }
+
+ if (cur->name) {
+ guchar *name = NULL;
+
+ name = (guchar *) g_strndup (cur->name->stryng->str,
+ cur->name->stryng->len);
+ if (name) {
+ g_string_append (str_buf, (const gchar *) name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+
+ if (cur->value) {
+ guchar *value = NULL;
+
+ value = (guchar *) g_strndup (cur->value->stryng->str,
+ cur->value->stryng->len);
+ if (value) {
+ switch (cur->match_way) {
+ case SET:
+ break;
+
+ case EQUALS:
+ g_string_append_c (str_buf, '=');
+ break;
+
+ case INCLUDES:
+ g_string_append (str_buf, "~=");
+ break;
+
+ case DASHMATCH:
+ g_string_append (str_buf, "|=");
+ break;
+
+ default:
+ break;
+ }
+
+ g_string_append_printf
+ (str_buf, "\"%s\"", value);
+
+ g_free (value);
+ value = NULL;
+ }
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_attr_sel_dump:
+ * @a_this: the "this pointer" of the current instance of
+ * #CRAttrSel.
+ * @a_fp: the destination file.
+ *
+ * Dumps the current instance of #CRAttrSel to a file.
+ */
+void
+cr_attr_sel_dump (CRAttrSel const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ g_return_if_fail (a_this);
+
+ tmp_str = cr_attr_sel_to_string (a_this);
+
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+}
+
+/**
+ *cr_attr_sel_destroy:
+ *@a_this: the "this pointer" of the current
+ *instance of #CRAttrSel.
+ *
+ *Destroys the current instance of #CRAttrSel.
+ *Frees all the fields if they are non null.
+ */
+void
+cr_attr_sel_destroy (CRAttrSel * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->name) {
+ cr_string_destroy (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (a_this->value) {
+ cr_string_destroy (a_this->value);
+ a_this->value = NULL;
+ }
+
+ if (a_this->next) {
+ cr_attr_sel_destroy (a_this->next);
+ a_this->next = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ a_this = NULL;
+ }
+}
+
diff --git a/src/st/croco/cr-attr-sel.h b/src/st/croco/cr-attr-sel.h
new file mode 100644
index 0000000..82d5a87
--- /dev/null
+++ b/src/st/croco/cr-attr-sel.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_ATTR_SEL_H__
+#define __CR_ATTR_SEL_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+#include "cr-string.h"
+
+G_BEGIN_DECLS
+
+
+struct _CRAttrSel ;
+typedef struct _CRAttrSel CRAttrSel ;
+
+enum AttrMatchWay
+{
+ NO_MATCH = 0,
+ SET,
+ EQUALS,
+ INCLUDES,
+ DASHMATCH
+} ;
+
+struct _CRAttrSel
+{
+ CRString *name ;
+ CRString *value ;
+ enum AttrMatchWay match_way ;
+ CRAttrSel *next ;
+ CRAttrSel *prev ;
+ CRParsingLocation location ;
+} ;
+
+CRAttrSel * cr_attr_sel_new (void) ;
+
+enum CRStatus cr_attr_sel_append_attr_sel (CRAttrSel * a_this,
+ CRAttrSel *a_attr_sel) ;
+
+enum CRStatus cr_attr_sel_prepend_attr_sel (CRAttrSel *a_this,
+ CRAttrSel *a_attr_sel) ;
+
+guchar * cr_attr_sel_to_string (CRAttrSel const *a_this) ;
+
+void cr_attr_sel_dump (CRAttrSel const *a_this, FILE *a_fp) ;
+
+void cr_attr_sel_destroy (CRAttrSel *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_ATTR_SEL_H__*/
diff --git a/src/st/croco/cr-cascade.c b/src/st/croco/cr-cascade.c
new file mode 100644
index 0000000..68f59bb
--- /dev/null
+++ b/src/st/croco/cr-cascade.c
@@ -0,0 +1,215 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the
+ * GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the
+ * GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+#include <string.h>
+#include "cr-cascade.h"
+
+#define PRIVATE(a_this) ((a_this)->priv)
+
+struct _CRCascadePriv {
+ /**
+ *the 3 style sheets of the cascade:
+ *author, user, and useragent sheet.
+ *Intended to be addressed by
+ *sheets[ORIGIN_AUTHOR] or sheets[ORIGIN_USER]
+ *of sheets[ORIGIN_UA] ;
+ */
+ CRStyleSheet *sheets[3];
+ guint ref_count;
+};
+
+/**
+ * cr_cascade_new:
+ *@a_author_sheet: the author origin style sheet. May be NULL.
+ *@a_user_sheet: the user origin style sheet. May be NULL.
+ *@a_ua_sheet: the user agent origin style sheet. May be NULL.
+ *
+ *Constructor of the #CRCascade class.
+ *Note that all three parameters of this
+ *method are ref counted and their refcount is increased.
+ *Their refcount will be decreased at the destruction of
+ *the instance of #CRCascade.
+ *So the caller should not call their destructor. The caller
+ *should call their ref/unref method instead if it wants
+ *
+ *Returns the newly built instance of CRCascade or NULL if
+ *an error arose during construction.
+ */
+CRCascade *
+cr_cascade_new (CRStyleSheet * a_author_sheet,
+ CRStyleSheet * a_user_sheet, CRStyleSheet * a_ua_sheet)
+{
+ CRCascade *result = NULL;
+
+ result = g_try_malloc (sizeof (CRCascade));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRCascade));
+
+ PRIVATE (result) = g_try_malloc (sizeof (CRCascadePriv));
+ if (!PRIVATE (result)) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (PRIVATE (result), 0, sizeof (CRCascadePriv));
+
+ if (a_author_sheet) {
+ cr_cascade_set_sheet (result, a_author_sheet, ORIGIN_AUTHOR);
+ }
+ if (a_user_sheet) {
+ cr_cascade_set_sheet (result, a_user_sheet, ORIGIN_USER);
+ }
+ if (a_ua_sheet) {
+ cr_cascade_set_sheet (result, a_ua_sheet, ORIGIN_UA);
+ }
+
+ return result;
+}
+
+/**
+ * cr_cascade_get_sheet:
+ *@a_this: the current instance of #CRCascade.
+ *@a_origin: the origin of the style sheet as
+ *defined in the css2 spec in chapter 6.4.
+ *Gets a given origin sheet.
+ *
+ *Gets a sheet, part of the cascade.
+ *Note that the returned stylesheet
+ *is refcounted so if the caller wants
+ *to manage it's lifecycle, it must use
+ *cr_stylesheet_ref()/cr_stylesheet_unref() instead
+ *of the cr_stylesheet_destroy() method.
+ *Returns the style sheet, or NULL if it does not
+ *exist.
+ */
+CRStyleSheet *
+cr_cascade_get_sheet (CRCascade * a_this, enum CRStyleOrigin a_origin)
+{
+ g_return_val_if_fail (a_this
+ && a_origin >= ORIGIN_UA
+ && a_origin < NB_ORIGINS, NULL);
+
+ return PRIVATE (a_this)->sheets[a_origin];
+}
+
+/**
+ * cr_cascade_set_sheet:
+ *@a_this: the current instance of #CRCascade.
+ *@a_sheet: the stylesheet to set.
+ *@a_origin: the origin of the stylesheet.
+ *
+ *Sets a stylesheet in the cascade
+ *
+ *Returns CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_cascade_set_sheet (CRCascade * a_this,
+ CRStyleSheet * a_sheet, enum CRStyleOrigin a_origin)
+{
+ g_return_val_if_fail (a_this
+ && a_sheet
+ && a_origin >= ORIGIN_UA
+ && a_origin < NB_ORIGINS, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->sheets[a_origin])
+ cr_stylesheet_unref (PRIVATE (a_this)->sheets[a_origin]);
+ PRIVATE (a_this)->sheets[a_origin] = a_sheet;
+ cr_stylesheet_ref (a_sheet);
+ a_sheet->origin = a_origin;
+ return CR_OK;
+}
+
+/**
+ *cr_cascade_ref:
+ *@a_this: the current instance of #CRCascade
+ *
+ *Increases the reference counter of the current instance
+ *of #CRCascade.
+ */
+void
+cr_cascade_ref (CRCascade * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ PRIVATE (a_this)->ref_count++;
+}
+
+/**
+ * cr_cascade_unref:
+ *@a_this: the current instance of
+ *#CRCascade.
+ *
+ *Decrements the reference counter associated
+ *to this instance of #CRCascade. If the reference
+ *counter reaches zero, the instance is destroyed
+ *using cr_cascade_destroy()
+ */
+void
+cr_cascade_unref (CRCascade * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ if (PRIVATE (a_this)->ref_count)
+ PRIVATE (a_this)->ref_count--;
+ if (!PRIVATE (a_this)->ref_count) {
+ cr_cascade_destroy (a_this);
+ }
+}
+
+/**
+ * cr_cascade_destroy:
+ * @a_this: the current instance of #CRCascade
+ *
+ * Destructor of #CRCascade.
+ */
+void
+cr_cascade_destroy (CRCascade * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (PRIVATE (a_this)) {
+ gulong i = 0;
+
+ for (i = 0; i < NB_ORIGINS; i++) {
+ if (PRIVATE (a_this)->sheets[i]) {
+ if (cr_stylesheet_unref
+ (PRIVATE (a_this)->sheets[i])
+ == TRUE) {
+ PRIVATE (a_this)->sheets[i] = NULL;
+ }
+ }
+ }
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-cascade.h b/src/st/croco/cr-cascade.h
new file mode 100644
index 0000000..3119ae8
--- /dev/null
+++ b/src/st/croco/cr-cascade.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the
+ * GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the
+ * GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ */
+
+/*
+ *$Id$
+ */
+
+#ifndef __CR_CASCADE_H__
+#define __CR_CASCADE_H__
+
+#include "cr-stylesheet.h"
+
+/**
+ *@file
+ *the declaration of the #CRCascade class.
+ */
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRCascadePriv CRCascadePriv ;
+
+/**
+ *An abstraction of the "Cascade" defined
+ *in the css2 spec, chapter 6.4.
+ */
+typedef struct _CRCascade CRCascade ;
+
+struct _CRCascade
+{
+ CRCascadePriv *priv ;
+};
+
+
+CRCascade * cr_cascade_new (CRStyleSheet *a_author_sheet,
+ CRStyleSheet *a_user_sheet,
+ CRStyleSheet *a_ua_sheet) ;
+
+CRStyleSheet * cr_cascade_get_sheet (CRCascade *a_this,
+ enum CRStyleOrigin a_origin) ;
+
+enum CRStatus cr_cascade_set_sheet (CRCascade *a_this,
+ CRStyleSheet *a_sheet,
+ enum CRStyleOrigin a_origin) ;
+
+void cr_cascade_ref (CRCascade *a_this) ;
+
+void cr_cascade_unref (CRCascade *a_this) ;
+
+void cr_cascade_destroy (CRCascade *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_CASCADE_H__*/
diff --git a/src/st/croco/cr-declaration.c b/src/st/croco/cr-declaration.c
new file mode 100644
index 0000000..6c70128
--- /dev/null
+++ b/src/st/croco/cr-declaration.c
@@ -0,0 +1,798 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See COPYRIGHTS file for copyright information.
+ */
+
+
+#include <string.h>
+#include "cr-declaration.h"
+#include "cr-statement.h"
+#include "cr-parser.h"
+
+/**
+ *@CRDeclaration:
+ *
+ *The definition of the #CRDeclaration class.
+ */
+
+/**
+ * dump:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white char.
+ *
+ *Dumps (serializes) one css declaration to a file.
+ */
+static void
+dump (CRDeclaration const * a_this, FILE * a_fp, glong a_indent)
+{
+ guchar *str = NULL;
+
+ g_return_if_fail (a_this);
+
+ str = (guchar *) cr_declaration_to_string (a_this, a_indent);
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+}
+
+/**
+ * cr_declaration_new:
+ * @a_statement: the statement this declaration belongs to. can be NULL.
+ *@a_property: the property string of the declaration
+ *@a_value: the value expression of the declaration.
+ *Constructor of #CRDeclaration.
+ *
+ *Returns the newly built instance of #CRDeclaration, or NULL in
+ *case of error.
+ *
+ *The returned CRDeclaration takes ownership of @a_property and @a_value.
+ *(E.g. cr_declaration_destroy on this CRDeclaration will also free
+ *@a_property and @a_value.)
+ */
+CRDeclaration *
+cr_declaration_new (CRStatement * a_statement,
+ CRString * a_property, CRTerm * a_value)
+{
+ CRDeclaration *result = NULL;
+
+ g_return_val_if_fail (a_property, NULL);
+
+ if (a_statement)
+ g_return_val_if_fail (a_statement
+ && ((a_statement->type == RULESET_STMT)
+ || (a_statement->type
+ == AT_FONT_FACE_RULE_STMT)
+ || (a_statement->type
+ == AT_PAGE_RULE_STMT)), NULL);
+
+ result = g_try_malloc (sizeof (CRDeclaration));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRDeclaration));
+ result->property = a_property;
+ result->value = a_value;
+
+ if (a_value) {
+ cr_term_ref (a_value);
+ }
+ result->parent_statement = a_statement;
+ return result;
+}
+
+/**
+ * cr_declaration_parse_from_buf:
+ *@a_statement: the parent css2 statement of this
+ *this declaration. Must be non NULL and of type
+ *RULESET_STMT (must be a ruleset).
+ *@a_str: the string that contains the statement.
+ *@a_enc: the encoding of a_str.
+ *
+ *Parses a text buffer that contains
+ *a css declaration.
+ *Returns the parsed declaration, or NULL in case of error.
+ */
+CRDeclaration *
+cr_declaration_parse_from_buf (CRStatement * a_statement,
+ const guchar * a_str, enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_OK;
+ CRTerm *value = NULL;
+ CRString *property = NULL;
+ CRDeclaration *result = NULL;
+ CRParser *parser = NULL;
+ gboolean important = FALSE;
+
+ g_return_val_if_fail (a_str, NULL);
+ if (a_statement)
+ g_return_val_if_fail (a_statement->type == RULESET_STMT,
+ NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_str, strlen ((const char *) a_str), a_enc, FALSE);
+ g_return_val_if_fail (parser, NULL);
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_declaration (parser, &property,
+ &value, &important);
+ if (status != CR_OK || !property)
+ goto cleanup;
+
+ result = cr_declaration_new (a_statement, property, value);
+ if (result) {
+ property = NULL;
+ value = NULL;
+ result->important = important;
+ }
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+
+ if (value) {
+ cr_term_destroy (value);
+ value = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_parse_list_from_buf:
+ *@a_str: the input buffer that contains the list of declaration to
+ *parse.
+ *@a_enc: the encoding of a_str
+ *
+ *Parses a ';' separated list of properties declaration.
+ *Returns the parsed list of declaration, NULL if parsing failed.
+ */
+CRDeclaration *
+cr_declaration_parse_list_from_buf (const guchar * a_str,
+ enum CREncoding a_enc)
+{
+
+ enum CRStatus status = CR_OK;
+ CRTerm *value = NULL;
+ CRString *property = NULL;
+ CRDeclaration *result = NULL,
+ *cur_decl = NULL;
+ CRParser *parser = NULL;
+ CRTknzr *tokenizer = NULL;
+ gboolean important = FALSE;
+
+ g_return_val_if_fail (a_str, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_str, strlen ((const char *) a_str), a_enc, FALSE);
+ g_return_val_if_fail (parser, NULL);
+ status = cr_parser_get_tknzr (parser, &tokenizer);
+ if (status != CR_OK || !tokenizer) {
+ if (status == CR_OK)
+ status = CR_ERROR;
+ goto cleanup;
+ }
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_declaration (parser, &property,
+ &value, &important);
+ if (status != CR_OK || !property) {
+ if (status != CR_OK)
+ status = CR_ERROR;
+ goto cleanup;
+ }
+ result = cr_declaration_new (NULL, property, value);
+ if (result) {
+ property = NULL;
+ value = NULL;
+ result->important = important;
+ }
+ /*now, go parse the other declarations */
+ for (;;) {
+ guint32 c = 0;
+
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ status = cr_tknzr_peek_char (tokenizer, &c);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR)
+ status = CR_OK;
+ goto cleanup;
+ }
+ if (c == ';') {
+ status = cr_tknzr_read_char (tokenizer, &c);
+ } else {
+ break;
+ }
+ important = FALSE;
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ status = cr_parser_parse_declaration (parser, &property,
+ &value, &important);
+ if (status != CR_OK || !property) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ }
+ break;
+ }
+ cur_decl = cr_declaration_new (NULL, property, value);
+ if (cur_decl) {
+ cur_decl->important = important;
+ result = cr_declaration_append (result, cur_decl);
+ property = NULL;
+ value = NULL;
+ cur_decl = NULL;
+ } else {
+ break;
+ }
+ }
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+
+ if (value) {
+ cr_term_destroy (value);
+ value = NULL;
+ }
+
+ if (status != CR_OK && result) {
+ cr_declaration_destroy (result);
+ result = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_declaration_append:
+ *@a_this: the current declaration list.
+ *@a_new: the declaration to append.
+ *
+ *Appends a new declaration to the current declarations list.
+ *Returns the declaration list with a_new appended to it, or NULL
+ *in case of error.
+ */
+CRDeclaration *
+cr_declaration_append (CRDeclaration * a_this, CRDeclaration * a_new)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this)
+ return a_new;
+
+ for (cur = a_this; cur && cur->next; cur = cur->next) ;
+
+ cur->next = a_new;
+ a_new->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_declaration_unlink:
+ *@a_decls: the declaration to unlink.
+ *
+ *Unlinks the declaration from the declaration list.
+ *case of a successful completion, NULL otherwise.
+ *
+ *Returns a pointer to the unlinked declaration in
+ */
+CRDeclaration *
+cr_declaration_unlink (CRDeclaration * a_decl)
+{
+ CRDeclaration *result = a_decl;
+
+ g_return_val_if_fail (result, NULL);
+
+ /*
+ *some sanity checks first
+ */
+ if (a_decl->prev) {
+ g_return_val_if_fail (a_decl->prev->next == a_decl, NULL);
+
+ }
+ if (a_decl->next) {
+ g_return_val_if_fail (a_decl->next->prev == a_decl, NULL);
+ }
+
+ /*
+ *now, the real unlinking job.
+ */
+ if (a_decl->prev) {
+ a_decl->prev->next = a_decl->next;
+ }
+ if (a_decl->next) {
+ a_decl->next->prev = a_decl->prev;
+ }
+ if (a_decl->parent_statement) {
+ CRDeclaration **children_decl_ptr = NULL;
+
+ switch (a_decl->parent_statement->type) {
+ case RULESET_STMT:
+ if (a_decl->parent_statement->kind.ruleset) {
+ children_decl_ptr =
+ &a_decl->parent_statement->
+ kind.ruleset->decl_list;
+ }
+
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ if (a_decl->parent_statement->kind.font_face_rule) {
+ children_decl_ptr =
+ &a_decl->parent_statement->
+ kind.font_face_rule->decl_list;
+ }
+ break;
+ case AT_PAGE_RULE_STMT:
+ if (a_decl->parent_statement->kind.page_rule) {
+ children_decl_ptr =
+ &a_decl->parent_statement->
+ kind.page_rule->decl_list;
+ }
+
+ default:
+ break;
+ }
+ if (children_decl_ptr
+ && *children_decl_ptr && *children_decl_ptr == a_decl)
+ *children_decl_ptr = (*children_decl_ptr)->next;
+ }
+
+ a_decl->next = NULL;
+ a_decl->prev = NULL;
+ a_decl->parent_statement = NULL;
+
+ return result;
+}
+
+/**
+ * cr_declaration_prepend:
+ * @a_this: the current declaration list.
+ * @a_new: the declaration to prepend.
+ *
+ * prepends a declaration to the current declaration list.
+ *
+ * Returns the list with a_new prepended or NULL in case of error.
+ */
+CRDeclaration *
+cr_declaration_prepend (CRDeclaration * a_this, CRDeclaration * a_new)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this)
+ return a_new;
+
+ a_this->prev = a_new;
+ a_new->next = a_this;
+
+ for (cur = a_new; cur && cur->prev; cur = cur->prev) ;
+
+ return cur;
+}
+
+/**
+ * cr_declaration_append2:
+ *@a_this: the current declaration list.
+ *@a_prop: the property string of the declaration to append.
+ *@a_value: the value of the declaration to append.
+ *
+ *Appends a declaration to the current declaration list.
+ *Returns the list with the new property appended to it, or NULL in
+ *case of an error.
+ */
+CRDeclaration *
+cr_declaration_append2 (CRDeclaration * a_this,
+ CRString * a_prop, CRTerm * a_value)
+{
+ CRDeclaration *new_elem = NULL;
+
+ if (a_this) {
+ new_elem = cr_declaration_new (a_this->parent_statement,
+ a_prop, a_value);
+ } else {
+ new_elem = cr_declaration_new (NULL, a_prop, a_value);
+ }
+
+ g_return_val_if_fail (new_elem, NULL);
+
+ return cr_declaration_append (a_this, new_elem);
+}
+
+/**
+ * cr_declaration_dump:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_fp: the destination file.
+ *@a_indent: the number of indentation white char.
+ *@a_one_per_line: whether to put one declaration per line of not .
+ *
+ *
+ *Dumps a declaration list to a file.
+ */
+void
+cr_declaration_dump (CRDeclaration const * a_this, FILE * a_fp, glong a_indent,
+ gboolean a_one_per_line)
+{
+ CRDeclaration const *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->prev) {
+ if (a_one_per_line == TRUE)
+ fprintf (a_fp, ";\n");
+ else
+ fprintf (a_fp, "; ");
+ }
+ dump (cur, a_fp, a_indent);
+ }
+}
+
+/**
+ * cr_declaration_dump_one:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_fp: the destination file.
+ *@a_indent: the number of indentation white char.
+ *
+ *Dumps the first declaration of the declaration list to a file.
+ */
+void
+cr_declaration_dump_one (CRDeclaration const * a_this, FILE * a_fp, glong a_indent)
+{
+ g_return_if_fail (a_this);
+
+ dump (a_this, a_fp, a_indent);
+}
+
+/**
+ * cr_declaration_to_string:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_indent: the number of indentation white char
+ *to put before the actual serialisation.
+ *
+ *Serializes the declaration into a string
+ *Returns the serialized form the declaration. The caller must
+ *free the string using g_free().
+ */
+gchar *
+cr_declaration_to_string (CRDeclaration const * a_this, gulong a_indent)
+{
+ GString *stringue = NULL;
+
+ gchar *str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ stringue = g_string_new (NULL);
+
+ if (a_this->property
+ && a_this->property->stryng
+ && a_this->property->stryng->str) {
+ str = g_strndup (a_this->property->stryng->str,
+ a_this->property->stryng->len);
+ if (str) {
+ cr_utils_dump_n_chars2 (' ', stringue,
+ a_indent);
+ g_string_append (stringue, str);
+ g_free (str);
+ str = NULL;
+ } else
+ goto error;
+
+ if (a_this->value) {
+ guchar *value_str = NULL;
+
+ value_str = cr_term_to_string (a_this->value);
+ if (value_str) {
+ g_string_append_printf (stringue, " : %s",
+ value_str);
+ g_free (value_str);
+ } else
+ goto error;
+ }
+ if (a_this->important == TRUE) {
+ g_string_append_printf (stringue, " %s",
+ "!important");
+ }
+ }
+ if (stringue && stringue->str) {
+ result = g_string_free (stringue, FALSE);
+ }
+ return result;
+
+ error:
+ if (stringue) {
+ g_string_free (stringue, TRUE);
+ stringue = NULL;
+ }
+ if (str) {
+ g_free (str);
+ str = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_list_to_string:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_indent: the number of indentation white char
+ *to put before the actual serialisation.
+ *
+ *Serializes the declaration list into a string
+ */
+guchar *
+cr_declaration_list_to_string (CRDeclaration const * a_this, gulong a_indent)
+{
+ CRDeclaration const *cur = NULL;
+ GString *stringue = NULL;
+ guchar *str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ stringue = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ str = (guchar *) cr_declaration_to_string (cur, a_indent);
+ if (str) {
+ g_string_append_printf (stringue, "%s;", str);
+ g_free (str);
+ } else
+ break;
+ }
+ if (stringue && stringue->str) {
+ result = (guchar *) g_string_free (stringue, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_list_to_string2:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_indent: the number of indentation white char
+ *@a_one_decl_per_line: whether to output one doc per line or not.
+ *to put before the actual serialisation.
+ *
+ *Serializes the declaration list into a string
+ *Returns the serialized form the declararation.
+ */
+guchar *
+cr_declaration_list_to_string2 (CRDeclaration const * a_this,
+ gulong a_indent, gboolean a_one_decl_per_line)
+{
+ CRDeclaration const *cur = NULL;
+ GString *stringue = NULL;
+ guchar *str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ stringue = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ str = (guchar *) cr_declaration_to_string (cur, a_indent);
+ if (str) {
+ if (a_one_decl_per_line == TRUE) {
+ if (cur->next)
+ g_string_append_printf (stringue,
+ "%s;\n", str);
+ else
+ g_string_append (stringue,
+ (const gchar *) str);
+ } else {
+ if (cur->next)
+ g_string_append_printf (stringue,
+ "%s;", str);
+ else
+ g_string_append (stringue,
+ (const gchar *) str);
+ }
+ g_free (str);
+ } else
+ break;
+ }
+ if (stringue && stringue->str) {
+ result = (guchar *) g_string_free (stringue, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_nr_props:
+ *@a_this: the current instance of #CRDeclaration.
+ *Return the number of properties in the declaration
+ */
+gint
+cr_declaration_nr_props (CRDeclaration const * a_this)
+{
+ CRDeclaration const *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, -1);
+
+ for (cur = a_this; cur; cur = cur->next)
+ nr++;
+ return nr;
+}
+
+/**
+ * cr_declaration_get_from_list:
+ *@a_this: the current instance of #CRDeclaration.
+ *@itemnr: the index into the declaration list.
+ *
+ *Use an index to get a CRDeclaration from the declaration list.
+ *
+ *Returns #CRDeclaration at position itemnr,
+ *if itemnr > number of declarations - 1,
+ *it will return NULL.
+ */
+CRDeclaration *
+cr_declaration_get_from_list (CRDeclaration * a_this, int itemnr)
+{
+ CRDeclaration *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ for (cur = a_this; cur; cur = cur->next)
+ if (nr++ == itemnr)
+ return cur;
+ return NULL;
+}
+
+/**
+ * cr_declaration_get_by_prop_name:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_prop: the property name to search for.
+ *
+ *Use property name to get a CRDeclaration from the declaration list.
+ *Returns #CRDeclaration with property name a_prop, or NULL if not found.
+ */
+CRDeclaration *
+cr_declaration_get_by_prop_name (CRDeclaration * a_this,
+ const guchar * a_prop)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+ g_return_val_if_fail (a_prop, NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->property
+ && cur->property->stryng
+ && cur->property->stryng->str) {
+ if (!strcmp (cur->property->stryng->str,
+ (const char *) a_prop)) {
+ return cur;
+ }
+ }
+ }
+ return NULL;
+}
+
+/**
+ * cr_declaration_ref:
+ *@a_this: the current instance of #CRDeclaration.
+ *
+ *Increases the ref count of the current instance of #CRDeclaration.
+ */
+void
+cr_declaration_ref (CRDeclaration * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ * cr_declaration_unref:
+ *@a_this: the current instance of #CRDeclaration.
+ *
+ *Decrements the ref count of the current instance of #CRDeclaration.
+ *If the ref count reaches zero, the current instance of #CRDeclaration
+ *if destroyed.
+ *Returns TRUE if @a_this was destroyed (ref count reached zero),
+ *FALSE otherwise.
+ */
+gboolean
+cr_declaration_unref (CRDeclaration * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_declaration_destroy (a_this);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * cr_declaration_destroy:
+ *@a_this: the current instance of #CRDeclaration.
+ *
+ *Destructor of the declaration list.
+ */
+void
+cr_declaration_destroy (CRDeclaration * a_this)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ /*
+ * Go to the last element of the list.
+ */
+ for (cur = a_this; cur->next; cur = cur->next)
+ g_assert (cur->next->prev == cur);
+
+ /*
+ * Walk backward the list and free each "next" element.
+ * Meanwhile, free each property/value pair contained in the list.
+ */
+ for (; cur; cur = cur->prev) {
+ g_free (cur->next);
+ cur->next = NULL;
+
+ if (cur->property) {
+ cr_string_destroy (cur->property);
+ cur->property = NULL;
+ }
+
+ if (cur->value) {
+ cr_term_destroy (cur->value);
+ cur->value = NULL;
+ }
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-declaration.h b/src/st/croco/cr-declaration.h
new file mode 100644
index 0000000..eee8be3
--- /dev/null
+++ b/src/st/croco/cr-declaration.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_DECLARATION_H__
+#define __CR_DECLARATION_H__
+
+#include <stdio.h>
+#include "cr-utils.h"
+#include "cr-term.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the #CRDeclaration class.
+ */
+
+/*forward declaration of what is defined in cr-statement.h*/
+typedef struct _CRStatement CRStatement ;
+
+/**
+ *The abstraction of a css declaration defined by the
+ *css2 spec in chapter 4.
+ *It is actually a chained list of property/value pairs.
+ */
+typedef struct _CRDeclaration CRDeclaration ;
+struct _CRDeclaration
+{
+ /**The property.*/
+ CRString *property ;
+
+ /**The value of the property.*/
+ CRTerm *value ;
+
+ /*the ruleset that contains this declaration*/
+ CRStatement *parent_statement ;
+
+ /*the next declaration*/
+ CRDeclaration *next ;
+
+ /*the previous one declaration*/
+ CRDeclaration *prev ;
+
+ /*does the declaration have the important keyword ?*/
+ gboolean important ;
+
+ glong ref_count ;
+
+ CRParsingLocation location ;
+ /*reserved for future usage*/
+ gpointer rfu0 ;
+ gpointer rfu1 ;
+ gpointer rfu2 ;
+ gpointer rfu3 ;
+} ;
+
+
+CRDeclaration * cr_declaration_new (CRStatement *a_statement,
+ CRString *a_property,
+ CRTerm *a_value) ;
+
+
+CRDeclaration * cr_declaration_parse_from_buf (CRStatement *a_statement,
+ const guchar *a_str,
+ enum CREncoding a_enc) ;
+
+CRDeclaration * cr_declaration_parse_list_from_buf (const guchar *a_str,
+ enum CREncoding a_enc) ;
+
+CRDeclaration * cr_declaration_append (CRDeclaration *a_this,
+ CRDeclaration *a_new) ;
+
+CRDeclaration * cr_declaration_append2 (CRDeclaration *a_this,
+ CRString *a_prop,
+ CRTerm *a_value) ;
+
+CRDeclaration * cr_declaration_prepend (CRDeclaration *a_this,
+ CRDeclaration *a_new) ;
+
+CRDeclaration * cr_declaration_unlink (CRDeclaration * a_decl) ;
+
+void
+cr_declaration_dump (CRDeclaration const *a_this,
+ FILE *a_fp, glong a_indent,
+ gboolean a_one_per_line) ;
+
+void cr_declaration_dump_one (CRDeclaration const *a_this,
+ FILE *a_fp, glong a_indent) ;
+
+gint cr_declaration_nr_props (CRDeclaration const *a_this) ;
+
+CRDeclaration * cr_declaration_get_from_list (CRDeclaration *a_this,
+ int itemnr) ;
+
+CRDeclaration * cr_declaration_get_by_prop_name (CRDeclaration *a_this,
+ const guchar *a_str) ;
+
+gchar * cr_declaration_to_string (CRDeclaration const *a_this,
+ gulong a_indent) ;
+
+guchar * cr_declaration_list_to_string (CRDeclaration const *a_this,
+ gulong a_indent) ;
+
+guchar * cr_declaration_list_to_string2 (CRDeclaration const *a_this,
+ gulong a_indent,
+ gboolean a_one_decl_per_line) ;
+
+void cr_declaration_ref (CRDeclaration *a_this) ;
+
+gboolean cr_declaration_unref (CRDeclaration *a_this) ;
+
+void cr_declaration_destroy (CRDeclaration *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_DECLARATION_H__*/
diff --git a/src/st/croco/cr-doc-handler.c b/src/st/croco/cr-doc-handler.c
new file mode 100644
index 0000000..b0ef13c
--- /dev/null
+++ b/src/st/croco/cr-doc-handler.c
@@ -0,0 +1,276 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPRYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-doc-handler.h"
+#include "cr-parser.h"
+
+/**
+ *@CRDocHandler:
+ *
+ *The definition of the CRDocHandler class.
+ *Contains methods to instantiate, destroy,
+ *and initialize instances of #CRDocHandler
+ *to custom values.
+ */
+
+#define PRIVATE(obj) (obj)->priv
+
+struct _CRDocHandlerPriv {
+ /**
+ *This pointer is to hold an application parsing context.
+ *For example, it used by the Object Model parser to
+ *store it parsing context. #CRParser does not touch it, but
+ *#CROMParser does. #CROMParser allocates this pointer at
+ *the beginning of the css document, and frees it at the end
+ *of the document.
+ */
+ gpointer context;
+
+ /**
+ *The place where #CROMParser puts the result of its parsing, if
+ *any.
+ */
+ gpointer result;
+ /**
+ *a pointer to the parser used to parse
+ *the current document.
+ */
+ CRParser *parser ;
+};
+
+/**
+ * cr_doc_handler_new:
+ *Constructor of #CRDocHandler.
+ *
+ *Returns the newly built instance of
+ *#CRDocHandler
+ *
+ */
+CRDocHandler *
+cr_doc_handler_new (void)
+{
+ CRDocHandler *result = NULL;
+
+ result = g_try_malloc (sizeof (CRDocHandler));
+
+ g_return_val_if_fail (result, NULL);
+
+ memset (result, 0, sizeof (CRDocHandler));
+ result->ref_count++;
+
+ result->priv = g_try_malloc (sizeof (CRDocHandlerPriv));
+ if (!result->priv) {
+ cr_utils_trace_info ("Out of memory exception");
+ g_free (result);
+ return NULL;
+ }
+
+ cr_doc_handler_set_default_sac_handler (result);
+
+ return result;
+}
+
+/**
+ * cr_doc_handler_get_ctxt:
+ *@a_this: the current instance of #CRDocHandler.
+ *@a_ctxt: out parameter. The new parsing context.
+ *
+ *Gets the private parsing context associated to the document handler
+ *The private parsing context is used by libcroco only.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_get_ctxt (CRDocHandler const * a_this, gpointer * a_ctxt)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+
+ *a_ctxt = a_this->priv->context;
+
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_set_ctxt:
+ *@a_this: the current instance of #CRDocHandler
+ *@a_ctxt: a pointer to the parsing context.
+ *
+ *Sets the private parsing context.
+ *This is used by libcroco only.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_set_ctxt (CRDocHandler * a_this, gpointer a_ctxt)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+ a_this->priv->context = a_ctxt;
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_get_result:
+ *@a_this: the current instance of #CRDocHandler
+ *@a_result: out parameter. The returned result.
+ *
+ *Gets the private parsing result.
+ *The private parsing result is used by libcroco only.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_get_result (CRDocHandler const * a_this, gpointer * a_result)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+
+ *a_result = a_this->priv->result;
+
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_set_result:
+ *@a_this: the current instance of #CRDocHandler
+ *@a_result: the new result.
+ *
+ *Sets the private parsing context.
+ *This is used by libcroco only.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_set_result (CRDocHandler * a_this, gpointer a_result)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+ a_this->priv->result = a_result;
+ return CR_OK;
+}
+
+/**
+ *cr_doc_handler_set_default_sac_handler:
+ *@a_this: a pointer to the current instance of #CRDocHandler.
+ *
+ *Sets the sac handlers contained in the current
+ *instance of DocHandler to the default handlers.
+ *For the time being the default handlers are
+ *test handlers. This is expected to change in a
+ *near future, when the libcroco gets a bit debugged.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_set_default_sac_handler (CRDocHandler * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ a_this->start_document = NULL;
+ a_this->end_document = NULL;
+ a_this->import_style = NULL;
+ a_this->namespace_declaration = NULL;
+ a_this->comment = NULL;
+ a_this->start_selector = NULL;
+ a_this->end_selector = NULL;
+ a_this->property = NULL;
+ a_this->start_font_face = NULL;
+ a_this->end_font_face = NULL;
+ a_this->start_media = NULL;
+ a_this->end_media = NULL;
+ a_this->start_page = NULL;
+ a_this->end_page = NULL;
+ a_this->ignorable_at_rule = NULL;
+ a_this->error = NULL;
+ a_this->unrecoverable_error = NULL;
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_ref:
+ *@a_this: the current instance of #CRDocHandler.
+ */
+void
+cr_doc_handler_ref (CRDocHandler * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ * cr_doc_handler_unref:
+ *@a_this: the current instance of #CRDocHandler.
+ *
+ *Decreases the ref count of the current instance of #CRDocHandler.
+ *If the ref count reaches '0' then, destroys the instance.
+ *
+ *Returns TRUE if the instance as been destroyed, FALSE otherwise.
+ */
+gboolean
+cr_doc_handler_unref (CRDocHandler * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count > 0) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_doc_handler_destroy (a_this);
+ return TRUE;
+ }
+ return FALSE ;
+}
+
+/**
+ * cr_doc_handler_destroy:
+ *@a_this: the instance of #CRDocHandler to
+ *destroy.
+ *
+ *The destructor of the #CRDocHandler class.
+ */
+void
+cr_doc_handler_destroy (CRDocHandler * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->priv) {
+ g_free (a_this->priv);
+ a_this->priv = NULL;
+ }
+ g_free (a_this);
+}
+
+/**
+ * cr_doc_handler_associate_a_parser:
+ *Associates a parser to the current document handler
+ *
+ *@a_this: the current instance of document handler.
+ *@a_parser: the parser to associate.
+ */
+void
+cr_doc_handler_associate_a_parser (CRDocHandler *a_this,
+ gpointer a_parser)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this)
+ && a_parser) ;
+
+ PRIVATE (a_this)->parser = a_parser ;
+}
diff --git a/src/st/croco/cr-doc-handler.h b/src/st/croco/cr-doc-handler.h
new file mode 100644
index 0000000..d12673f
--- /dev/null
+++ b/src/st/croco/cr-doc-handler.h
@@ -0,0 +1,298 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_DOC_HANDLER_H__
+#define __CR_DOC_HANDLER_H__
+
+/**
+ *@file
+ *The declaration of the #CRDocumentHandler class.
+ *This class is actually the parsing events handler.
+ */
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-input.h"
+#include "cr-stylesheet.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRDocHandler CRDocHandler ;
+
+struct _CRDocHandlerPriv ;
+typedef struct _CRDocHandlerPriv CRDocHandlerPriv ;
+
+
+/**
+ *The SAC document handler.
+ *An instance of this class is to
+ *be passed to a parser. Then, during the parsing
+ *the parser calls the convenient function pointer
+ *whenever a particular event (a css construction) occurs.
+ */
+struct _CRDocHandler
+{
+ CRDocHandlerPriv *priv ;
+
+ /**
+ *This pointer is to be used by the application for
+ *it custom needs. It is there to extend the doc handler.
+ */
+ gpointer app_data ;
+
+ /**
+ *Is called at the beginning of the parsing of the document.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ */
+ void (*start_document) (CRDocHandler *a_this) ;
+
+ /**
+ *Is called to notify the end of the parsing of the document.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ */
+ void (*end_document) (CRDocHandler *a_this) ;
+
+ /**
+ *Is called to notify an at charset rule.
+ *@param a_this the document handler.
+ *@param a_charset the declared charset.
+ */
+ void (*charset) (CRDocHandler *a_this,
+ CRString *a_charset,
+ CRParsingLocation *a_charset_sym_location) ;
+
+ /**
+ *Is called to notify an import statement in
+ *the stylesheet.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_media_list a doubly linked list of GString objects.
+ *Each GString object contains a string which is the
+ *destination media for style information.
+ *@param a_uri the uri of the imported style sheet.
+ *@param a_uri_default_ns the default namespace of URI
+ *@param a_location the parsing location of the '\@import'
+ *keyword.
+ *of the imported style sheet.
+ */
+ void (*import_style) (CRDocHandler *a_this,
+ GList *a_media_list,
+ CRString *a_uri,
+ CRString *a_uri_default_ns,
+ CRParsingLocation *a_location) ;
+
+ void (*import_style_result) (CRDocHandler *a_this,
+ GList *a_media_list,
+ CRString *a_uri,
+ CRString *a_uri_default_ns,
+ CRStyleSheet *a_sheet) ;
+
+ /**
+ *Is called to notify a namespace declaration.
+ *Not used yet.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_prefix the prefix of the namespace.
+ *@param a_uri the uri of the namespace.
+ *@param a_location the location of the "@namespace" keyword.
+ */
+ void (*namespace_declaration) (CRDocHandler *a_this,
+ CRString *a_prefix,
+ CRString *a_uri,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify a comment.
+ *@param a_this a pointer to the current instance
+ *of #CRDocHandler.
+ *@param a_comment the comment.
+ */
+ void (*comment) (CRDocHandler *a_this,
+ CRString *a_comment) ;
+
+ /**
+ *Is called to notify the beginning of a rule
+ *statement.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_selector_list the list of selectors that precedes
+ *the rule declarations.
+ */
+ void (*start_selector) (CRDocHandler * a_this,
+ CRSelector *a_selector_list) ;
+
+ /**
+ *Is called to notify the end of a rule statement.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_selector_list the list of selectors that precedes
+ *the rule declarations. This pointer is the same as
+ *the one passed to start_selector() ;
+ */
+ void (*end_selector) (CRDocHandler *a_this,
+ CRSelector *a_selector_list) ;
+
+
+ /**
+ *Is called to notify a declaration.
+ *@param a_this a pointer to the current instance
+ *of #CRDocHandler.
+ *@param a_name the name of the parsed property.
+ *@param a_expression a css expression that represents
+ *the value of the property. A css expression is
+ *actually a linked list of 'terms'. Each term can
+ *be linked to other using operators.
+ *
+ */
+ void (*property) (CRDocHandler *a_this,
+ CRString *a_name,
+ CRTerm *a_expression,
+ gboolean a_is_important) ;
+ /**
+ *Is called to notify the start of a font face statement.
+ *The parser invokes this method at the beginning of every
+ *font face statement in the style sheet. There will
+ *be a corresponding end_font_face () event for every
+ *start_font_face () event.
+ *
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_location the parsing location of the "\@font-face"
+ *keyword.
+ */
+ void (*start_font_face) (CRDocHandler *a_this,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify the end of a font face statement.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ */
+ void (*end_font_face) (CRDocHandler *a_this) ;
+
+
+ /**
+ *Is called to notify the beginning of a media statement.
+ *The parser will invoke this method at the beginning of
+ *every media statement in the style sheet. There will be
+ *a corresponding end_media() event for every start_media()
+ *event.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_media_list a double linked list of
+ #CRString * objects.
+ *Each CRString objects is actually a destination media for
+ *the style information.
+ */
+ void (*start_media) (CRDocHandler *a_this,
+ GList *a_media_list,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify the end of a media statement.
+ *@param a_this a pointer to the current instance
+ *of #CRDocHandler.
+ *@param a_media_list a double linked list of GString * objects.
+ *Each GString objects is actually a destination media for
+ *the style information.
+ */
+ void (*end_media) (CRDocHandler *a_this,
+ GList *a_media_list) ;
+
+ /**
+ *Is called to notify the beginning of a page statement.
+ *The parser invokes this function at the beginning of
+ *every page statement in the style sheet. There will be
+ *a corresponding end_page() event for every single
+ *start_page() event.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_name the name of the page (if any, null otherwise).
+ *@param a_pseudo_page the pseudo page (if any, null otherwise).
+ *@param a_location the parsing location of the "\@page" keyword.
+ */
+ void (*start_page) (CRDocHandler *a_this,
+ CRString *a_name,
+ CRString *a_pseudo_page,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify the end of a page statement.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_name the name of the page (if any, null otherwise).
+ *@param a_pseudo_page the pseudo page (if any, null otherwise).
+ */
+ void (*end_page) (CRDocHandler *a_this,
+ CRString *a_name,
+ CRString *pseudo_page) ;
+
+ /**
+ *Is Called to notify an unknown at-rule not supported
+ *by this parser.
+ */
+ void (*ignorable_at_rule) (CRDocHandler *a_this,
+ CRString *a_name) ;
+
+ /**
+ *Is called to notify a parsing error. After this error
+ *the application must ignore the rule being parsed, if
+ *any. After completion of this callback,
+ *the parser will then try to resume the parsing,
+ *ignoring the current error.
+ */
+ void (*error) (CRDocHandler *a_this) ;
+
+ /**
+ *Is called to notify an unrecoverable parsing error.
+ *This is the place to put emergency routines that free allocated
+ *resources.
+ */
+ void (*unrecoverable_error) (CRDocHandler *a_this) ;
+
+ gboolean resolve_import ;
+ gulong ref_count ;
+} ;
+
+CRDocHandler * cr_doc_handler_new (void) ;
+
+enum CRStatus cr_doc_handler_set_result (CRDocHandler *a_this, gpointer a_result) ;
+
+enum CRStatus cr_doc_handler_get_result (CRDocHandler const *a_this, gpointer * a_result) ;
+
+enum CRStatus cr_doc_handler_set_ctxt (CRDocHandler *a_this, gpointer a_ctxt) ;
+
+enum CRStatus cr_doc_handler_get_ctxt (CRDocHandler const *a_this, gpointer * a_ctxt) ;
+
+enum CRStatus cr_doc_handler_set_default_sac_handler (CRDocHandler *a_this) ;
+
+void cr_doc_handler_associate_a_parser (CRDocHandler *a_this,
+ gpointer a_parser) ;
+
+void cr_doc_handler_ref (CRDocHandler *a_this) ;
+
+gboolean cr_doc_handler_unref (CRDocHandler *a_this) ;
+
+void cr_doc_handler_destroy (CRDocHandler *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_DOC_HANDLER_H__*/
diff --git a/src/st/croco/cr-enc-handler.c b/src/st/croco/cr-enc-handler.c
new file mode 100644
index 0000000..65adc7a
--- /dev/null
+++ b/src/st/croco/cr-enc-handler.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+/**
+ *@file
+ *The definition of the #CREncHandler class.
+ */
+
+#include "cr-enc-handler.h"
+#include "cr-utils.h"
+
+#include <string.h>
+
+struct CREncAlias {
+ const gchar *name;
+ enum CREncoding encoding;
+};
+
+static struct CREncAlias gv_default_aliases[] = {
+ {"UTF-8", CR_UTF_8},
+ {"UTF_8", CR_UTF_8},
+ {"UTF8", CR_UTF_8},
+ {"UTF-16", CR_UTF_16},
+ {"UTF_16", CR_UTF_16},
+ {"UTF16", CR_UTF_16},
+ {"UCS1", CR_UCS_1},
+ {"UCS-1", CR_UCS_1},
+ {"UCS_1", CR_UCS_1},
+ {"ISO-8859-1", CR_UCS_1},
+ {"ISO_8859-1", CR_UCS_1},
+ {"UCS-1", CR_UCS_1},
+ {"UCS_1", CR_UCS_1},
+ {"UCS4", CR_UCS_4},
+ {"UCS-4", CR_UCS_4},
+ {"UCS_4", CR_UCS_4},
+ {"ASCII", CR_ASCII},
+ {0, 0}
+};
+
+static CREncHandler gv_default_enc_handlers[] = {
+ {CR_UCS_1, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1,
+ cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1},
+
+ {CR_ISO_8859_1, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1,
+ cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1},
+
+ {CR_ASCII, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1,
+ cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1},
+
+ {0, NULL, NULL, NULL, NULL}
+};
+
+/**
+ * cr_enc_handler_get_instance:
+ *@a_enc: the encoding of the Handler.
+ *
+ *Gets the instance of encoding handler.
+ *This function implements a singleton pattern.
+ *
+ *Returns the instance of #CREncHandler.
+ */
+CREncHandler *
+cr_enc_handler_get_instance (enum CREncoding a_enc)
+{
+ gulong i = 0;
+
+ for (i = 0; gv_default_enc_handlers[i].encoding; i++) {
+ if (gv_default_enc_handlers[i].encoding == a_enc) {
+ return (CREncHandler *) & gv_default_enc_handlers[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * cr_enc_handler_resolve_enc_alias:
+ *@a_alias_name: the encoding name.
+ *@a_enc: output param. The returned encoding type
+ *or 0 if the alias is not supported.
+ *
+ *Given an encoding name (called an alias name)
+ *the function returns the matching encoding type.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_enc_handler_resolve_enc_alias (const guchar * a_alias_name,
+ enum CREncoding *a_enc)
+{
+ gulong i = 0;
+ guchar *alias_name_up = NULL;
+ enum CRStatus status = CR_ENCODING_NOT_FOUND_ERROR;
+
+ g_return_val_if_fail (a_alias_name != NULL, CR_BAD_PARAM_ERROR);
+
+ alias_name_up = (guchar *) g_ascii_strup ((const gchar *) a_alias_name, -1);
+
+ for (i = 0; gv_default_aliases[i].name; i++) {
+ if (!strcmp (gv_default_aliases[i].name, (const gchar *) alias_name_up)) {
+ *a_enc = gv_default_aliases[i].encoding;
+ status = CR_OK;
+ break;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * cr_enc_handler_convert_input:
+ *@a_this: the current instance of #CREncHandler.
+ *@a_in: the input buffer to convert.
+ *@a_in_len: in/out parameter. The len of the input
+ *buffer to convert. After return, contains the number of
+ *bytes actually consumed.
+ *@a_out: output parameter. The converted output buffer.
+ *Must be freed by the buffer.
+ *@a_out_len: output parameter. The length of the output buffer.
+ *
+ *Converts a raw input buffer into an utf8 buffer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_enc_handler_convert_input (CREncHandler * a_this,
+ const guchar * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && a_in && a_in_len && a_out,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->decode_input == NULL)
+ return CR_OK;
+
+ if (a_this->enc_str_len_as_utf8) {
+ status = a_this->enc_str_len_as_utf8 (a_in,
+ &a_in[*a_in_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+ } else {
+ *a_out_len = *a_in_len;
+ }
+
+ *a_out = g_malloc0 (*a_out_len);
+
+ status = a_this->decode_input (a_in, a_in_len, *a_out, a_out_len);
+
+ if (status != CR_OK) {
+ g_free (*a_out);
+ *a_out = NULL;
+ }
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ return CR_OK;
+}
diff --git a/src/st/croco/cr-enc-handler.h b/src/st/croco/cr-enc-handler.h
new file mode 100644
index 0000000..0727764
--- /dev/null
+++ b/src/st/croco/cr-enc-handler.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+/**
+ *@file:
+ *The declaration of the #CREncHandler class.
+ *
+ */
+
+#ifndef __CR_ENC_HANDLER_H__
+#define __CR_ENC_HANDLER_H__
+
+#include "cr-utils.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CREncHandler CREncHandler ;
+
+typedef enum CRStatus (*CREncInputFunc) (const guchar * a_in,
+ gulong *a_in_len,
+ guchar *a_out,
+ gulong *a_out_len) ;
+
+typedef enum CRStatus (*CREncOutputFunc) (const guchar * a_in,
+ gulong *a_in_len,
+ guchar *a_out,
+ gulong *a_out_len) ;
+
+typedef enum CRStatus (*CREncInputStrLenAsUtf8Func)
+(const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_in_size);
+
+typedef enum CRStatus (*CREncUtf8StrLenAsOutputFunc)
+(const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_in_size) ;
+
+/**
+ *This class is responsible of the
+ *the encoding conversions stuffs in
+ *libcroco.
+ */
+
+struct _CREncHandler
+{
+ enum CREncoding encoding ;
+ CREncInputFunc decode_input ;
+ CREncInputFunc encode_output ;
+ CREncInputStrLenAsUtf8Func enc_str_len_as_utf8 ;
+ CREncUtf8StrLenAsOutputFunc utf8_str_len_as_enc ;
+} ;
+
+CREncHandler *
+cr_enc_handler_get_instance (enum CREncoding a_enc) ;
+
+enum CRStatus
+cr_enc_handler_resolve_enc_alias (const guchar *a_alias_name,
+ enum CREncoding *a_enc) ;
+
+enum CRStatus
+cr_enc_handler_convert_input (CREncHandler *a_this,
+ const guchar *a_in,
+ gulong *a_in_len,
+ guchar **a_out,
+ gulong *a_out_len) ;
+
+G_END_DECLS
+
+#endif /*__CR_ENC_HANDLER_H__*/
diff --git a/src/st/croco/cr-fonts.c b/src/st/croco/cr-fonts.c
new file mode 100644
index 0000000..a64ffc0
--- /dev/null
+++ b/src/st/croco/cr-fonts.c
@@ -0,0 +1,948 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of
+ * the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the
+ * GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ *See COPYRIGHTS file for copyright information
+ */
+
+#include "cr-fonts.h"
+#include <string.h>
+
+static enum CRStatus
+cr_font_family_to_string_real (CRFontFamily const * a_this,
+ gboolean a_walk_list, GString ** a_string)
+{
+ guchar const *name = NULL;
+ enum CRStatus result = CR_OK;
+
+ if (!*a_string) {
+ *a_string = g_string_new (NULL);
+ g_return_val_if_fail (*a_string,
+ CR_INSTANCIATION_FAILED_ERROR);
+ }
+
+ if (!a_this) {
+ g_string_append (*a_string, "NULL");
+ return CR_OK;
+ }
+
+ switch (a_this->type) {
+ case FONT_FAMILY_SANS_SERIF:
+ name = (guchar const *) "sans-serif";
+ break;
+
+ case FONT_FAMILY_SERIF:
+ name = (guchar const *) "sans-serif";
+ break;
+
+ case FONT_FAMILY_CURSIVE:
+ name = (guchar const *) "cursive";
+ break;
+
+ case FONT_FAMILY_FANTASY:
+ name = (guchar const *) "fantasy";
+ break;
+
+ case FONT_FAMILY_MONOSPACE:
+ name = (guchar const *) "monospace";
+ break;
+
+ case FONT_FAMILY_NON_GENERIC:
+ name = (guchar const *) a_this->name;
+ break;
+
+ default:
+ name = NULL;
+ break;
+ }
+
+ if (name) {
+ if (a_this->prev) {
+ g_string_append_printf (*a_string, ", %s", name);
+ } else {
+ g_string_append (*a_string, (const gchar *) name);
+ }
+ }
+ if (a_walk_list == TRUE && a_this->next) {
+ result = cr_font_family_to_string_real (a_this->next,
+ TRUE, a_string);
+ }
+ return result;
+}
+
+static const gchar *
+cr_predefined_absolute_font_size_to_string (enum CRPredefinedAbsoluteFontSize
+ a_code)
+{
+ gchar const *str = NULL;
+
+ switch (a_code) {
+ case FONT_SIZE_XX_SMALL:
+ str = "xx-small";
+ break;
+ case FONT_SIZE_X_SMALL:
+ str = "x-small";
+ break;
+ case FONT_SIZE_SMALL:
+ str = "small";
+ break;
+ case FONT_SIZE_MEDIUM:
+ str = "medium";
+ break;
+ case FONT_SIZE_LARGE:
+ str = "large";
+ break;
+ case FONT_SIZE_X_LARGE:
+ str = "x-large";
+ break;
+ case FONT_SIZE_XX_LARGE:
+ str = "xx-large";
+ break;
+ default:
+ str = "unknown absolute font size value";
+ }
+ return str;
+}
+
+static const gchar *
+cr_relative_font_size_to_string (enum CRRelativeFontSize a_code)
+{
+ gchar const *str = NULL;
+
+ switch (a_code) {
+ case FONT_SIZE_LARGER:
+ str = "larger";
+ break;
+ case FONT_SIZE_SMALLER:
+ str = "smaller";
+ break;
+ default:
+ str = "unknown relative font size value";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_family_new:
+ * @a_type: the type of font family to create.
+ * @a_name: the name of the font family.
+ *
+ * create a font family.
+ *
+ * Returns the newly built font family.
+ */
+CRFontFamily *
+cr_font_family_new (enum CRFontFamilyType a_type, guchar * a_name)
+{
+ CRFontFamily *result = NULL;
+
+ result = g_try_malloc (sizeof (CRFontFamily));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRFontFamily));
+ result->type = a_type;
+
+ cr_font_family_set_name (result, a_name);
+
+ return result;
+}
+
+/**
+ * cr_font_family_to_string:
+ * @a_this: the current instance of #CRFontFamily.
+ * @a_walk_font_family_list: whether the serialize the entire list.
+ *
+ * Returns the seriliazed font family. The caller has to free it using
+ * g_free().
+ */
+guchar *
+cr_font_family_to_string (CRFontFamily const * a_this,
+ gboolean a_walk_font_family_list)
+{
+ enum CRStatus status = CR_OK;
+ guchar *result = NULL;
+ GString *stringue = NULL;
+
+ if (!a_this) {
+ result = (guchar *) g_strdup ("NULL");
+ g_return_val_if_fail (result, NULL);
+ return result;
+ }
+ status = cr_font_family_to_string_real (a_this,
+ a_walk_font_family_list,
+ &stringue);
+
+ if (status == CR_OK && stringue) {
+ result = (guchar *) g_string_free (stringue, FALSE);
+ stringue = NULL;
+
+ } else {
+ if (stringue) {
+ g_string_free (stringue, TRUE);
+ stringue = NULL;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * cr_font_family_set_name:
+ * @a_this: the current instance of #CRFontFamily.
+ * @a_name: the new name
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_family_set_name (CRFontFamily * a_this, guchar * a_name)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ /*
+ *only non generic font families can have a name
+ */
+
+ if (a_this->type != FONT_FAMILY_NON_GENERIC) {
+ return CR_BAD_PARAM_ERROR;
+ }
+
+ if (a_this->name) {
+ g_free (a_this->name);
+ a_this->name = NULL;
+ }
+
+ a_this->name = a_name;
+ return CR_OK;
+}
+
+/**
+ * cr_font_family_append:
+ * @a_this: the current instance of #CRFontFamily.
+ * @a_family_to_append: the font family to append to the list
+ *
+ * Returns the new font family list.
+ */
+CRFontFamily *
+cr_font_family_append (CRFontFamily * a_this,
+ CRFontFamily * a_family_to_append)
+{
+ CRFontFamily *cur_ff = NULL;
+
+ g_return_val_if_fail (a_family_to_append, NULL);
+
+ if (!a_this)
+ return a_family_to_append;
+
+ for (cur_ff = a_this; cur_ff && cur_ff->next; cur_ff = cur_ff->next) ;
+
+ cur_ff->next = a_family_to_append;
+ a_family_to_append->prev = cur_ff;
+
+ return a_this;
+
+}
+
+/**
+ * cr_font_family_prepend:
+ * @a_this: the current instance #CRFontFamily.
+ * @a_family_to_prepend: the font family to prepend to the list.
+ *
+ * Returns the font family list.
+ */
+CRFontFamily *
+cr_font_family_prepend (CRFontFamily * a_this,
+ CRFontFamily * a_family_to_prepend)
+{
+ g_return_val_if_fail (a_this && a_family_to_prepend, NULL);
+
+ if (!a_this)
+ return a_family_to_prepend;
+
+ a_family_to_prepend->next = a_this;
+ a_this->prev = a_family_to_prepend;
+
+ return a_family_to_prepend;
+}
+
+/**
+ * cr_font_family_destroy:
+ * @a_this: the current instance of #CRFontFamily.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_family_destroy (CRFontFamily * a_this)
+{
+ CRFontFamily *cur_ff = NULL;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ for (cur_ff = a_this; cur_ff && cur_ff->next; cur_ff = cur_ff->next) ;
+
+ for (; cur_ff; cur_ff = cur_ff->prev) {
+ if (a_this->name) {
+ g_free (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (cur_ff->next) {
+ g_free (cur_ff->next);
+
+ }
+
+ if (cur_ff->prev == NULL) {
+ g_free (a_this);
+ }
+ }
+
+ return CR_OK;
+}
+
+/***************************************************
+ *'font-size' manipulation functions definitions
+ ***************************************************/
+
+/**
+ * cr_font_size_new:
+ *
+ * Returns the newly created font size.
+ */
+CRFontSize *
+cr_font_size_new (void)
+{
+ CRFontSize *result = NULL;
+
+ result = g_try_malloc (sizeof (CRFontSize));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRFontSize));
+
+ return result;
+}
+
+/**
+ * cr_font_size_clear:
+ * @a_this: the current instance of #CRFontSize
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_clear (CRFontSize * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ switch (a_this->type) {
+ case PREDEFINED_ABSOLUTE_FONT_SIZE:
+ case RELATIVE_FONT_SIZE:
+ case INHERITED_FONT_SIZE:
+ memset (a_this, 0, sizeof (CRFontSize));
+ break;
+
+ case ABSOLUTE_FONT_SIZE:
+ memset (a_this, 0, sizeof (CRFontSize));
+ break;
+
+ default:
+ return CR_UNKNOWN_TYPE_ERROR;
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_font_size_copy:
+ * @a_dst: the destination #CRFontSize (where to copy to).
+ * @a_src: the source #CRFontSize (where to copy from).
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_copy (CRFontSize * a_dst, CRFontSize const * a_src)
+{
+ g_return_val_if_fail (a_dst && a_src, CR_BAD_PARAM_ERROR);
+
+ switch (a_src->type) {
+ case PREDEFINED_ABSOLUTE_FONT_SIZE:
+ case RELATIVE_FONT_SIZE:
+ case INHERITED_FONT_SIZE:
+ cr_font_size_clear (a_dst);
+ memcpy (a_dst, a_src, sizeof (CRFontSize));
+ break;
+
+ case ABSOLUTE_FONT_SIZE:
+ cr_font_size_clear (a_dst);
+ cr_num_copy (&a_dst->value.absolute,
+ &a_src->value.absolute);
+ a_dst->type = a_src->type;
+ break;
+
+ default:
+ return CR_UNKNOWN_TYPE_ERROR;
+ }
+ return CR_OK;
+}
+
+/**
+ * cr_font_size_set_predefined_absolute_font_size:
+ * @a_this: the current instance of #CRFontSize.
+ * @a_predefined: what to set.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_predefined_absolute_font_size (CRFontSize *a_this,
+ enum CRPredefinedAbsoluteFontSize a_predefined)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+ g_return_val_if_fail (a_predefined >= FONT_SIZE_XX_SMALL
+ && a_predefined < NB_PREDEFINED_ABSOLUTE_FONT_SIZES,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_this->type = PREDEFINED_ABSOLUTE_FONT_SIZE ;
+ a_this->value.predefined = a_predefined ;
+
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_set_relative_font_size:
+ * @a_this: the current instance of #CRFontSize
+ * @a_relative: the new relative font size
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_relative_font_size (CRFontSize *a_this,
+ enum CRRelativeFontSize a_relative)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+ g_return_val_if_fail (a_relative >= FONT_SIZE_LARGER
+ && a_relative < NB_RELATIVE_FONT_SIZE,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_this->type = RELATIVE_FONT_SIZE ;
+ a_this->value.relative = a_relative ;
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_set_absolute_font_size:
+ * @a_this: the current instance of #CRFontSize
+ * @a_num_type: the type of number to set.
+ * @a_value: the actual value to set.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_absolute_font_size (CRFontSize *a_this,
+ enum CRNumType a_num_type,
+ gdouble a_value)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+ g_return_val_if_fail (a_num_type >= NUM_AUTO
+ && a_num_type < NB_NUM_TYPE,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_this->type = ABSOLUTE_FONT_SIZE ;
+ cr_num_set (&a_this->value.absolute,
+ a_value, a_num_type) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_set_to_inherit:
+ * @a_this: the current instance of #CRFontSize
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_to_inherit (CRFontSize *a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+
+ cr_font_size_clear (a_this) ;
+ a_this->type = INHERITED_FONT_SIZE ;
+
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_is_set_to_inherit:
+ * @a_this: the current instance of #CRFontSize.
+ *
+ * Returns TRUE if the current instance is set to 'inherit'.
+ */
+gboolean
+cr_font_size_is_set_to_inherit (CRFontSize const *a_this)
+{
+ g_return_val_if_fail (a_this, FALSE) ;
+
+ return a_this->type == INHERITED_FONT_SIZE ;
+}
+
+/**
+ * cr_font_size_to_string:
+ * @a_this: the current instance of #CRFontSize
+ *
+ * Returns the serialized form of #CRFontSize. The returned string
+ * has to bee freed using g_free().
+ */
+gchar *
+cr_font_size_to_string (CRFontSize const * a_this)
+{
+ gchar *str = NULL;
+
+ if (!a_this) {
+ str = g_strdup ("NULL");
+ g_return_val_if_fail (str, NULL);
+ return str;
+ }
+ switch (a_this->type) {
+ case PREDEFINED_ABSOLUTE_FONT_SIZE:
+ str = g_strdup (cr_predefined_absolute_font_size_to_string
+ (a_this->value.predefined));
+ break;
+ case ABSOLUTE_FONT_SIZE:
+ str = (gchar *) cr_num_to_string (&a_this->value.absolute);
+ break;
+ case RELATIVE_FONT_SIZE:
+ str = g_strdup (cr_relative_font_size_to_string
+ (a_this->value.relative));
+ break;
+ case INHERITED_FONT_SIZE:
+ str = g_strdup ("inherit");
+ break;
+ default:
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_size_get_smaller_predefined:
+ * @a_font_size: the font size to consider.
+ * @a_smaller_size: out parameter. The a smaller value than @a_font_size.
+ */
+void
+cr_font_size_get_smaller_predefined_font_size
+ (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_smaller_size)
+{
+ enum CRPredefinedAbsoluteFontSize result = FONT_SIZE_MEDIUM ;
+
+ g_return_if_fail (a_smaller_size) ;
+ g_return_if_fail (a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES
+ && a_font_size >= FONT_SIZE_XX_SMALL) ;
+
+ switch (a_font_size) {
+ case FONT_SIZE_XX_SMALL:
+ result = FONT_SIZE_XX_SMALL ;
+ break ;
+ case FONT_SIZE_X_SMALL:
+ result = FONT_SIZE_XX_SMALL ;
+ break ;
+ case FONT_SIZE_SMALL:
+ result = FONT_SIZE_X_SMALL;
+ break ;
+ case FONT_SIZE_MEDIUM:
+ result = FONT_SIZE_SMALL;
+ break ;
+ case FONT_SIZE_LARGE:
+ result = FONT_SIZE_MEDIUM;
+ break ;
+ case FONT_SIZE_X_LARGE:
+ result = FONT_SIZE_LARGE;
+ break ;
+ case FONT_SIZE_XX_LARGE:
+ result = FONT_SIZE_XX_LARGE;
+ break ;
+ case FONT_SIZE_INHERIT:
+ cr_utils_trace_info ("can't return a smaller size for FONT_SIZE_INHERIT") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ default:
+ cr_utils_trace_info ("Unknown FONT_SIZE") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ }
+ *a_smaller_size = result ;
+}
+
+
+/**
+ * cr_font_size_get_larger_predefined_font_size:
+ * @a_font_size: the font size to consider.
+ * @a_larger_size: out parameter. the font size considered larger than
+ * @a_font_size.
+ *
+ */
+void
+cr_font_size_get_larger_predefined_font_size
+ (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_larger_size)
+{
+ enum CRPredefinedAbsoluteFontSize result = FONT_SIZE_MEDIUM ;
+
+ g_return_if_fail (a_larger_size) ;
+ g_return_if_fail (a_font_size >= FONT_SIZE_XX_SMALL
+ && a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES) ;
+
+ switch (a_font_size) {
+ case FONT_SIZE_XX_SMALL:
+ result = FONT_SIZE_X_SMALL ;
+ break ;
+ case FONT_SIZE_X_SMALL:
+ result = FONT_SIZE_SMALL ;
+ break ;
+ case FONT_SIZE_SMALL:
+ result = FONT_SIZE_MEDIUM;
+ break ;
+ case FONT_SIZE_MEDIUM:
+ result = FONT_SIZE_LARGE;
+ break ;
+ case FONT_SIZE_LARGE:
+ result = FONT_SIZE_X_LARGE;
+ break ;
+ case FONT_SIZE_X_LARGE:
+ result = FONT_SIZE_XX_LARGE ;
+ break ;
+ case FONT_SIZE_XX_LARGE:
+ result = FONT_SIZE_XX_LARGE;
+ break ;
+ case FONT_SIZE_INHERIT:
+ cr_utils_trace_info ("can't return a bigger size for FONT_SIZE_INHERIT") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ default:
+ cr_utils_trace_info ("Unknown FONT_SIZE") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ }
+ *a_larger_size = result ;
+}
+
+/**
+ * cr_font_size_is_predefined_absolute_font_size:
+ * @a_font_size: the font size to consider.
+ *
+ * Returns TRUE if the instance is an predefined absolute font size, FALSE
+ * otherwise.
+ */
+gboolean
+cr_font_size_is_predefined_absolute_font_size
+ (enum CRPredefinedAbsoluteFontSize a_font_size)
+{
+ if (a_font_size >= FONT_SIZE_XX_SMALL
+ && a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES) {
+ return TRUE ;
+ } else {
+ return FALSE ;
+ }
+}
+
+/**
+ * cr_font_size_adjust_to_string:
+ * @a_this: the instance of #CRFontSizeAdjust.
+ *
+ * Returns the serialized form of #CRFontSizeAdjust
+ */
+gchar *
+cr_font_size_adjust_to_string (CRFontSizeAdjust const * a_this)
+{
+ gchar *str = NULL;
+
+ if (!a_this) {
+ str = g_strdup ("NULL");
+ g_return_val_if_fail (str, NULL);
+ return str;
+ }
+
+ switch (a_this->type) {
+ case FONT_SIZE_ADJUST_NONE:
+ str = g_strdup ("none");
+ break;
+ case FONT_SIZE_ADJUST_NUMBER:
+ if (a_this->num)
+ str = (gchar *) cr_num_to_string (a_this->num);
+ else
+ str = g_strdup ("unknown font-size-adjust property value"); /* Should raise an error no?*/
+ break;
+ case FONT_SIZE_ADJUST_INHERIT:
+ str = g_strdup ("inherit");
+ }
+ return str;
+}
+
+/**
+ * cr_font_style_to_string:
+ * @a_code: the current instance of #CRFontStyle .
+ *
+ * Returns the serialized #CRFontStyle. The caller must free the returned
+ * string using g_free().
+ */
+const gchar *
+cr_font_style_to_string (enum CRFontStyle a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_STYLE_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_STYLE_ITALIC:
+ str = (gchar *) "italic";
+ break;
+ case FONT_STYLE_OBLIQUE:
+ str = (gchar *) "oblique";
+ break;
+ case FONT_STYLE_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ default:
+ str = (gchar *) "unknown font style value";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_variant_to_string:
+ * @a_code: the current instance of #CRFontVariant.
+ *
+ * Returns the serialized form of #CRFontVariant. The caller has
+ * to free the returned string using g_free().
+ */
+const gchar *
+cr_font_variant_to_string (enum CRFontVariant a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_VARIANT_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_VARIANT_SMALL_CAPS:
+ str = (gchar *) "small-caps";
+ break;
+ case FONT_VARIANT_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_weight_get_bolder:
+ * @a_weight: the #CRFontWeight to consider.
+ *
+ * Returns a font weight bolder than @a_weight
+ */
+enum CRFontWeight
+cr_font_weight_get_bolder (enum CRFontWeight a_weight)
+{
+ if (a_weight == FONT_WEIGHT_INHERIT) {
+ cr_utils_trace_info ("can't return a bolder weight for FONT_WEIGHT_INHERIT") ;
+ return a_weight;
+ } else if (a_weight >= FONT_WEIGHT_900) {
+ return FONT_WEIGHT_900 ;
+ } else if (a_weight < FONT_WEIGHT_NORMAL) {
+ return FONT_WEIGHT_NORMAL ;
+ } else if (a_weight == FONT_WEIGHT_BOLDER
+ || a_weight == FONT_WEIGHT_LIGHTER) {
+ cr_utils_trace_info ("FONT_WEIGHT_BOLDER or FONT_WEIGHT_LIGHTER should not appear here") ;
+ return FONT_WEIGHT_NORMAL ;
+ } else {
+ return a_weight << 1 ;
+ }
+}
+
+/**
+ * cr_font_weight_to_string:
+ * @a_code: the font weight to consider.
+ *
+ * Returns the serialized form of #CRFontWeight.
+ */
+const gchar *
+cr_font_weight_to_string (enum CRFontWeight a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_WEIGHT_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_WEIGHT_BOLD:
+ str = (gchar *) "bold";
+ break;
+ case FONT_WEIGHT_BOLDER:
+ str = (gchar *) "bolder";
+ break;
+ case FONT_WEIGHT_LIGHTER:
+ str = (gchar *) "lighter";
+ break;
+ case FONT_WEIGHT_100:
+ str = (gchar *) "100";
+ break;
+ case FONT_WEIGHT_200:
+ str = (gchar *) "200";
+ break;
+ case FONT_WEIGHT_300:
+ str = (gchar *) "300";
+ break;
+ case FONT_WEIGHT_400:
+ str = (gchar *) "400";
+ break;
+ case FONT_WEIGHT_500:
+ str = (gchar *) "500";
+ break;
+ case FONT_WEIGHT_600:
+ str = (gchar *) "600";
+ break;
+ case FONT_WEIGHT_700:
+ str = (gchar *) "700";
+ break;
+ case FONT_WEIGHT_800:
+ str = (gchar *) "800";
+ break;
+ case FONT_WEIGHT_900:
+ str = (gchar *) "900";
+ break;
+ case FONT_WEIGHT_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ default:
+ str = (gchar *) "unknown font-weight property value";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_stretch_to_string:
+ * @a_code: the instance of #CRFontStretch to consider.
+ *
+ * Returns the serialized form of #CRFontStretch.
+ */
+const gchar *
+cr_font_stretch_to_string (enum CRFontStretch a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_STRETCH_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_STRETCH_WIDER:
+ str = (gchar *) "wider";
+ break;
+ case FONT_STRETCH_NARROWER:
+ str = (gchar *) "narrower";
+ break;
+ case FONT_STRETCH_ULTRA_CONDENSED:
+ str = (gchar *) "ultra-condensed";
+ break;
+ case FONT_STRETCH_EXTRA_CONDENSED:
+ str = (gchar *) "extra-condensed";
+ break;
+ case FONT_STRETCH_CONDENSED:
+ str = (gchar *) "condensed";
+ break;
+ case FONT_STRETCH_SEMI_CONDENSED:
+ str = (gchar *) "semi-condensed";
+ break;
+ case FONT_STRETCH_SEMI_EXPANDED:
+ str = (gchar *) "semi-expanded";
+ break;
+ case FONT_STRETCH_EXPANDED:
+ str = (gchar *) "expanded";
+ break;
+ case FONT_STRETCH_EXTRA_EXPANDED:
+ str = (gchar *) "extra-expaned";
+ break;
+ case FONT_STRETCH_ULTRA_EXPANDED:
+ str = (gchar *) "ultra-expanded";
+ break;
+ case FONT_STRETCH_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_size_destroy:
+ * @a_font_size: the font size to destroy
+ *
+ */
+void
+cr_font_size_destroy (CRFontSize * a_font_size)
+{
+ g_return_if_fail (a_font_size);
+
+ g_free (a_font_size) ;
+}
+
+/*******************************************************
+ *'font-size-adjust' manipulation function definition
+ *******************************************************/
+
+/**
+ * cr_font_size_adjust_new:
+ *
+ * Returns a newly built instance of #CRFontSizeAdjust
+ */
+CRFontSizeAdjust *
+cr_font_size_adjust_new (void)
+{
+ CRFontSizeAdjust *result = NULL;
+
+ result = g_try_malloc (sizeof (CRFontSizeAdjust));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRFontSizeAdjust));
+
+ return result;
+}
+
+/**
+ * cr_font_size_adjust_destroy:
+ * @a_this: the current instance of #CRFontSizeAdjust.
+ *
+ */
+void
+cr_font_size_adjust_destroy (CRFontSizeAdjust * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->type == FONT_SIZE_ADJUST_NUMBER && a_this->num) {
+ cr_num_destroy (a_this->num);
+ a_this->num = NULL;
+ }
+}
diff --git a/src/st/croco/cr-fonts.h b/src/st/croco/cr-fonts.h
new file mode 100644
index 0000000..9eaeeeb
--- /dev/null
+++ b/src/st/croco/cr-fonts.h
@@ -0,0 +1,315 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of
+ * the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the
+ * GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_FONTS_H__
+#define __CR_FONTS_H__
+
+#include "cr-utils.h"
+#include "cr-num.h"
+
+/**
+ *@file
+ *Various type declarations about font selection related
+ *properties.
+ */
+G_BEGIN_DECLS
+
+
+enum CRFontFamilyType
+{
+ FONT_FAMILY_SANS_SERIF,
+ FONT_FAMILY_SERIF,
+ FONT_FAMILY_CURSIVE,
+ FONT_FAMILY_FANTASY,
+ FONT_FAMILY_MONOSPACE,
+ FONT_FAMILY_NON_GENERIC,
+ FONT_FAMILY_INHERIT,
+ /**/
+ NB_FONT_FAMILIE_TYPES
+} ;
+
+typedef struct _CRFontFamily CRFontFamily ;
+
+struct _CRFontFamily
+{
+ enum CRFontFamilyType type ;
+
+ /*
+ *The name of the font family, in case
+ *it is non generic.
+ *Is set only if the type is FONT_FAMILY_NON_GENERIC.
+ */
+ guchar *name ;
+
+ CRFontFamily *next ;
+ CRFontFamily *prev ;
+} ;
+
+
+/**
+ *The different types
+ *of absolute font size.
+ *This is used by the 'font-size'
+ *property defined in css2 spec
+ *in chapter 15.2.4 .
+ *These values a indexes of
+ *table of size so please, do not
+ *change their definition order unless
+ *you know what you are doing.
+ */
+enum CRPredefinedAbsoluteFontSize
+{
+ FONT_SIZE_XX_SMALL=0,
+ FONT_SIZE_X_SMALL,
+ FONT_SIZE_SMALL,
+ FONT_SIZE_MEDIUM,
+ FONT_SIZE_LARGE,
+ FONT_SIZE_X_LARGE,
+ FONT_SIZE_XX_LARGE,
+ FONT_SIZE_INHERIT,
+ NB_PREDEFINED_ABSOLUTE_FONT_SIZES
+} ;
+
+/**
+ *The different types
+ *of relative font size.
+ *This is used by the 'font-size'
+ *property defined in css2 spec
+ *in chapter 15.2.4 .
+ *These values a indexes of
+ *table of size so please, do not
+ *change their definition order unless
+ *you know what you are doing.
+ */
+enum CRRelativeFontSize
+{
+ FONT_SIZE_LARGER,
+ FONT_SIZE_SMALLER,
+ NB_RELATIVE_FONT_SIZE
+} ;
+
+/**
+ *The type of font-size property.
+ *Used to define the type of #CRFontSize .
+ *See css2 spec chapter 15.2.4 to understand.
+ */
+enum CRFontSizeType {
+ /**
+ *If the type of #CRFontSize is
+ *PREDEFINED_ABSOLUTE_FONT_SIZE,
+ *the CRFontSize::value.predefined_absolute
+ *field will be defined.
+ */
+ PREDEFINED_ABSOLUTE_FONT_SIZE,
+
+ /**
+ *If the type of #CRFontSize is
+ *ABSOLUTE_FONT_SIZE,
+ *the CRFontSize::value.absolute
+ *field will be defined.
+ */
+ ABSOLUTE_FONT_SIZE,
+
+ /**
+ *If the type of #CRFontSize is
+ *RELATIVE_FONT_SIZE,
+ *the CRFontSize::value.relative
+ *field will be defined.
+ */
+ RELATIVE_FONT_SIZE,
+
+ /**
+ *If the type of #CRFontSize is
+ *INHERITED_FONT_SIZE,
+ *the None of the field of the CRFontSize::value enum
+ *will be defined.
+ */
+ INHERITED_FONT_SIZE,
+
+ NB_FONT_SIZE_TYPE
+} ;
+
+typedef struct _CRFontSize CRFontSize ;
+struct _CRFontSize {
+ enum CRFontSizeType type ;
+ union {
+ enum CRPredefinedAbsoluteFontSize predefined ;
+ enum CRRelativeFontSize relative ;
+ CRNum absolute ;
+ } value;
+} ;
+
+enum CRFontSizeAdjustType
+{
+ FONT_SIZE_ADJUST_NONE = 0,
+ FONT_SIZE_ADJUST_NUMBER,
+ FONT_SIZE_ADJUST_INHERIT
+} ;
+typedef struct _CRFontSizeAdjust CRFontSizeAdjust ;
+struct _CRFontSizeAdjust
+{
+ enum CRFontSizeAdjustType type ;
+ CRNum *num ;
+} ;
+
+enum CRFontStyle
+{
+ FONT_STYLE_NORMAL=0,
+ FONT_STYLE_ITALIC,
+ FONT_STYLE_OBLIQUE,
+ FONT_STYLE_INHERIT
+} ;
+
+enum CRFontVariant
+{
+ FONT_VARIANT_NORMAL=0,
+ FONT_VARIANT_SMALL_CAPS,
+ FONT_VARIANT_INHERIT
+} ;
+
+enum CRFontWeight
+{
+ FONT_WEIGHT_NORMAL = 1,
+ FONT_WEIGHT_BOLD = 1<<1,
+ FONT_WEIGHT_BOLDER = 1<<2,
+ FONT_WEIGHT_LIGHTER = 1<<3,
+ FONT_WEIGHT_100 = 1<<4,
+ FONT_WEIGHT_200 = 1<<5,
+ FONT_WEIGHT_300 = 1<<6,
+ FONT_WEIGHT_400 = 1<<7,
+ FONT_WEIGHT_500 = 1<<8,
+ FONT_WEIGHT_600 = 1<<9,
+ FONT_WEIGHT_700 = 1<<10,
+ FONT_WEIGHT_800 = 1<<11,
+ FONT_WEIGHT_900 = 1<<12,
+ FONT_WEIGHT_INHERIT = 1<<13,
+ NB_FONT_WEIGHTS
+} ;
+
+enum CRFontStretch
+{
+ FONT_STRETCH_NORMAL=0,
+ FONT_STRETCH_WIDER,
+ FONT_STRETCH_NARROWER,
+ FONT_STRETCH_ULTRA_CONDENSED,
+ FONT_STRETCH_EXTRA_CONDENSED,
+ FONT_STRETCH_CONDENSED,
+ FONT_STRETCH_SEMI_CONDENSED,
+ FONT_STRETCH_SEMI_EXPANDED,
+ FONT_STRETCH_EXPANDED,
+ FONT_STRETCH_EXTRA_EXPANDED,
+ FONT_STRETCH_ULTRA_EXPANDED,
+ FONT_STRETCH_INHERIT
+} ;
+
+/**************************************
+ *'font-family' manipulation functions
+ ***************************************/
+CRFontFamily *
+cr_font_family_new (enum CRFontFamilyType a_type, guchar *a_name) ;
+
+CRFontFamily *
+cr_font_family_append (CRFontFamily *a_this,
+ CRFontFamily *a_family_to_append) ;
+
+guchar *
+cr_font_family_to_string (CRFontFamily const *a_this,
+ gboolean a_walk_font_family_list) ;
+
+CRFontFamily *
+cr_font_family_prepend (CRFontFamily *a_this,
+ CRFontFamily *a_family_to_prepend);
+
+enum CRStatus
+cr_font_family_destroy (CRFontFamily *a_this) ;
+
+enum CRStatus
+cr_font_family_set_name (CRFontFamily *a_this, guchar *a_name) ;
+
+
+/************************************
+ *'font-size' manipulation functions
+ ***********************************/
+
+CRFontSize * cr_font_size_new (void) ;
+
+enum CRStatus cr_font_size_clear (CRFontSize *a_this) ;
+
+enum CRStatus cr_font_size_copy (CRFontSize *a_dst,
+ CRFontSize const *a_src) ;
+enum CRStatus cr_font_size_set_predefined_absolute_font_size (CRFontSize *a_this,
+ enum CRPredefinedAbsoluteFontSize a_predefined) ;
+enum CRStatus cr_font_size_set_relative_font_size (CRFontSize *a_this,
+ enum CRRelativeFontSize a_relative) ;
+
+enum CRStatus cr_font_size_set_absolute_font_size (CRFontSize *a_this,
+ enum CRNumType a_num_type,
+ gdouble a_value) ;
+
+enum CRStatus cr_font_size_set_to_inherit (CRFontSize *a_this) ;
+
+gboolean cr_font_size_is_set_to_inherit (CRFontSize const *a_this) ;
+
+gchar* cr_font_size_to_string (CRFontSize const *a_this) ;
+
+void cr_font_size_destroy (CRFontSize *a_font_size) ;
+
+/*******************************************************
+ *'font-size-adjust' manipulation function declarations
+ *******************************************************/
+
+CRFontSizeAdjust * cr_font_size_adjust_new (void) ;
+
+gchar * cr_font_size_adjust_to_string (CRFontSizeAdjust const *a_this) ;
+
+void cr_font_size_adjust_destroy (CRFontSizeAdjust *a_this) ;
+
+void
+cr_font_size_get_smaller_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_smaller_size) ;
+void
+cr_font_size_get_larger_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_larger_size) ;
+
+gboolean
+cr_font_size_is_predefined_absolute_font_size (enum CRPredefinedAbsoluteFontSize a_font_size) ;
+
+/***********************************
+ *various other font related functions
+ ***********************************/
+const gchar * cr_font_style_to_string (enum CRFontStyle a_code) ;
+
+const gchar * cr_font_weight_to_string (enum CRFontWeight a_code) ;
+
+enum CRFontWeight
+cr_font_weight_get_bolder (enum CRFontWeight a_weight) ;
+
+const gchar * cr_font_variant_to_string (enum CRFontVariant a_code) ;
+
+const gchar * cr_font_stretch_to_string (enum CRFontStretch a_code) ;
+
+G_END_DECLS
+
+#endif
diff --git a/src/st/croco/cr-input.c b/src/st/croco/cr-input.c
new file mode 100644
index 0000000..430e75e
--- /dev/null
+++ b/src/st/croco/cr-input.c
@@ -0,0 +1,1191 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include "stdio.h"
+#include <string.h>
+#include "cr-input.h"
+#include "cr-enc-handler.h"
+
+/**
+ *@CRInput:
+ *
+ *The definition of the #CRInput class.
+ */
+
+/*******************
+ *Private type defs
+ *******************/
+
+/**
+ *The private attributes of
+ *the #CRInputPriv class.
+ */
+struct _CRInputPriv {
+ /*
+ *The input buffer
+ */
+ guchar *in_buf;
+ gulong in_buf_size;
+
+ gulong nb_bytes;
+
+ /*
+ *The index of the next byte
+ *to be read.
+ */
+ gulong next_byte_index;
+
+ /*
+ *The current line number
+ */
+ gulong line;
+
+ /*
+ *The current col number
+ */
+ gulong col;
+
+ gboolean end_of_line;
+ gboolean end_of_input;
+
+ /*
+ *the reference count of this
+ *instance.
+ */
+ guint ref_count;
+ gboolean free_in_buf;
+};
+
+#define PRIVATE(object) (object)->priv
+
+/***************************
+ *private constants
+ **************************/
+#define CR_INPUT_MEM_CHUNK_SIZE 1024 * 4
+
+static CRInput *cr_input_new_real (void);
+
+static CRInput *
+cr_input_new_real (void)
+{
+ CRInput *result = NULL;
+
+ result = g_try_malloc (sizeof (CRInput));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRInput));
+
+ PRIVATE (result) = g_try_malloc (sizeof (CRInputPriv));
+ if (!PRIVATE (result)) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (PRIVATE (result), 0, sizeof (CRInputPriv));
+ PRIVATE (result)->free_in_buf = TRUE;
+ return result;
+}
+
+/****************
+ *Public methods
+ ***************/
+
+/**
+ * cr_input_new_from_buf:
+ *@a_buf: the memory buffer to create the input stream from.
+ *The #CRInput keeps this pointer so user should not free it !.
+ *@a_len: the size of the input buffer.
+ *@a_enc: the buffer's encoding.
+ *@a_free_buf: if set to TRUE, this a_buf will be freed
+ *at the destruction of this instance. If set to false, it is up
+ *to the caller to free it.
+ *
+ *Creates a new input stream from a memory buffer.
+ *Returns the newly built instance of #CRInput.
+ */
+CRInput *
+cr_input_new_from_buf (guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_buf)
+{
+ CRInput *result = NULL;
+ enum CRStatus status = CR_OK;
+ CREncHandler *enc_handler = NULL;
+ gulong len = a_len;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ result = cr_input_new_real ();
+ g_return_val_if_fail (result, NULL);
+
+ /*transform the encoding in utf8 */
+ if (a_enc != CR_UTF_8) {
+ enc_handler = cr_enc_handler_get_instance (a_enc);
+ if (!enc_handler) {
+ goto error;
+ }
+
+ status = cr_enc_handler_convert_input
+ (enc_handler, a_buf, &len,
+ &PRIVATE (result)->in_buf,
+ &PRIVATE (result)->in_buf_size);
+ if (status != CR_OK)
+ goto error;
+ PRIVATE (result)->free_in_buf = TRUE;
+ if (a_free_buf == TRUE && a_buf) {
+ g_free (a_buf) ;
+ a_buf = NULL ;
+ }
+ PRIVATE (result)->nb_bytes = PRIVATE (result)->in_buf_size;
+ } else {
+ PRIVATE (result)->in_buf = (guchar *) a_buf;
+ PRIVATE (result)->in_buf_size = a_len;
+ PRIVATE (result)->nb_bytes = a_len;
+ PRIVATE (result)->free_in_buf = a_free_buf;
+ }
+ PRIVATE (result)->line = 1;
+ PRIVATE (result)->col = 0;
+ return result;
+
+ error:
+ if (result) {
+ cr_input_destroy (result);
+ result = NULL;
+ }
+
+ return NULL;
+}
+
+/**
+ * cr_input_new_from_uri:
+ *@a_file_uri: the file to create *the input stream from.
+ *@a_enc: the encoding of the file *to create the input from.
+ *
+ *Creates a new input stream from
+ *a file.
+ *
+ *Returns the newly created input stream if
+ *this method could read the file and create it,
+ *NULL otherwise.
+ */
+
+CRInput *
+cr_input_new_from_uri (const gchar * a_file_uri, enum CREncoding a_enc)
+{
+ CRInput *result = NULL;
+ enum CRStatus status = CR_OK;
+ FILE *file_ptr = NULL;
+ guchar tmp_buf[CR_INPUT_MEM_CHUNK_SIZE] = { 0 };
+ gulong nb_read = 0,
+ len = 0,
+ buf_size = 0;
+ gboolean loop = TRUE;
+ guchar *buf = NULL;
+
+ g_return_val_if_fail (a_file_uri, NULL);
+
+ file_ptr = fopen (a_file_uri, "r");
+
+ if (file_ptr == NULL) {
+
+#ifdef CR_DEBUG
+ cr_utils_trace_debug ("could not open file");
+#endif
+ g_warning ("Could not open file %s\n", a_file_uri);
+
+ return NULL;
+ }
+
+ /*load the file */
+ while (loop) {
+ nb_read = fread (tmp_buf, 1 /*read bytes */ ,
+ CR_INPUT_MEM_CHUNK_SIZE /*nb of bytes */ ,
+ file_ptr);
+
+ if (nb_read != CR_INPUT_MEM_CHUNK_SIZE) {
+ /*we read less chars than we wanted */
+ if (feof (file_ptr)) {
+ /*we reached eof */
+ loop = FALSE;
+ } else {
+ /*a pb occurred !! */
+ cr_utils_trace_debug ("an io error occurred");
+ status = CR_ERROR;
+ goto cleanup;
+ }
+ }
+
+ if (status == CR_OK) {
+ /*read went well */
+ buf = g_realloc (buf, len + CR_INPUT_MEM_CHUNK_SIZE);
+ memcpy (buf + len, tmp_buf, nb_read);
+ len += nb_read;
+ buf_size += CR_INPUT_MEM_CHUNK_SIZE;
+ }
+ }
+
+ if (status == CR_OK) {
+ result = cr_input_new_from_buf (buf, len, a_enc, TRUE);
+ if (!result) {
+ goto cleanup;
+ }
+ /*
+ *we should free buf here because it's own by CRInput.
+ *(see the last parameter of cr_input_new_from_buf().
+ */
+ buf = NULL;
+ }
+
+ cleanup:
+ if (file_ptr) {
+ fclose (file_ptr);
+ file_ptr = NULL;
+ }
+
+ if (buf) {
+ g_free (buf);
+ buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_input_destroy:
+ *@a_this: the current instance of #CRInput.
+ *
+ *The destructor of the #CRInput class.
+ */
+void
+cr_input_destroy (CRInput * a_this)
+{
+ if (a_this == NULL)
+ return;
+
+ if (PRIVATE (a_this)) {
+ if (PRIVATE (a_this)->in_buf && PRIVATE (a_this)->free_in_buf) {
+ g_free (PRIVATE (a_this)->in_buf);
+ PRIVATE (a_this)->in_buf = NULL;
+ }
+
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ g_free (a_this);
+}
+
+/**
+ * cr_input_ref:
+ *@a_this: the current instance of #CRInput.
+ *
+ *Increments the reference count of the current
+ *instance of #CRInput.
+ */
+void
+cr_input_ref (CRInput * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ PRIVATE (a_this)->ref_count++;
+}
+
+/**
+ * cr_input_unref:
+ *@a_this: the current instance of #CRInput.
+ *
+ *Decrements the reference count of this instance
+ *of #CRInput. If the reference count goes down to
+ *zero, this instance is destroyed.
+ *
+ * Returns TRUE if the instance of #CRInput got destroyed, false otherwise.
+ */
+gboolean
+cr_input_unref (CRInput * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE);
+
+ if (PRIVATE (a_this)->ref_count) {
+ PRIVATE (a_this)->ref_count--;
+ }
+
+ if (PRIVATE (a_this)->ref_count == 0) {
+ cr_input_destroy (a_this);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * cr_input_end_of_input:
+ *@a_this: the current instance of #CRInput.
+ *@a_end_of_input: out parameter. Is set to TRUE if
+ *the current instance has reached the end of its input buffer,
+ *FALSE otherwise.
+ *
+ *Tests whether the current instance of
+ *#CRInput has reached its input buffer.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ * Note that all the out parameters of this method are valid if
+ * and only if this method returns CR_OK.
+ */
+enum CRStatus
+cr_input_end_of_input (CRInput const * a_this, gboolean * a_end_of_input)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_end_of_input, CR_BAD_PARAM_ERROR);
+
+ *a_end_of_input = (PRIVATE (a_this)->next_byte_index
+ >= PRIVATE (a_this)->in_buf_size) ? TRUE : FALSE;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_nb_bytes_left:
+ *@a_this: the current instance of #CRInput.
+ *
+ *Returns the number of bytes left in the input stream
+ *before the end, -1 in case of error.
+ */
+glong
+cr_input_get_nb_bytes_left (CRInput const * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), -1);
+ g_return_val_if_fail (PRIVATE (a_this)->nb_bytes
+ <= PRIVATE (a_this)->in_buf_size, -1);
+ g_return_val_if_fail (PRIVATE (a_this)->next_byte_index
+ <= PRIVATE (a_this)->nb_bytes, -1);
+
+ if (PRIVATE (a_this)->end_of_input)
+ return 0;
+
+ return PRIVATE (a_this)->nb_bytes - PRIVATE (a_this)->next_byte_index;
+}
+
+/**
+ * cr_input_read_byte:
+ *@a_this: the current instance of #CRInput.
+ *@a_byte: out parameter the returned byte.
+ *
+ *Gets the next byte of the input.
+ *Updates the state of the input so that
+ *the next invocation of this method returns
+ *the next coming byte.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise. All the out parameters of this method are valid if
+ *and only if this method returns CR_OK.
+ */
+enum CRStatus
+cr_input_read_byte (CRInput * a_this, guchar * a_byte)
+{
+ gulong nb_bytes_left = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_byte, CR_BAD_PARAM_ERROR);
+
+ g_return_val_if_fail (PRIVATE (a_this)->next_byte_index <=
+ PRIVATE (a_this)->nb_bytes, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->end_of_input == TRUE)
+ return CR_END_OF_INPUT_ERROR;
+
+ nb_bytes_left = cr_input_get_nb_bytes_left (a_this);
+
+ if (nb_bytes_left < 1) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ *a_byte = PRIVATE (a_this)->in_buf[PRIVATE (a_this)->next_byte_index];
+
+ if (PRIVATE (a_this)->nb_bytes -
+ PRIVATE (a_this)->next_byte_index < 2) {
+ PRIVATE (a_this)->end_of_input = TRUE;
+ } else {
+ PRIVATE (a_this)->next_byte_index++;
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_read_char:
+ *@a_this: the current instance of CRInput.
+ *@a_char: out parameter. The read character.
+ *
+ *Reads an unicode character from the current instance of
+ *#CRInput.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_read_char (CRInput * a_this, guint32 * a_char)
+{
+ enum CRStatus status = CR_OK;
+ gulong consumed = 0,
+ nb_bytes_left = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char,
+ CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->end_of_input == TRUE)
+ return CR_END_OF_INPUT_ERROR;
+
+ nb_bytes_left = cr_input_get_nb_bytes_left (a_this);
+
+ if (nb_bytes_left < 1) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ status = cr_utils_read_char_from_utf8_buf
+ (PRIVATE (a_this)->in_buf
+ +
+ PRIVATE (a_this)->next_byte_index,
+ nb_bytes_left, a_char, &consumed);
+
+ if (status == CR_OK) {
+ /*update next byte index */
+ PRIVATE (a_this)->next_byte_index += consumed;
+
+ /*update line and column number */
+ if (PRIVATE (a_this)->end_of_line == TRUE) {
+ PRIVATE (a_this)->col = 1;
+ PRIVATE (a_this)->line++;
+ PRIVATE (a_this)->end_of_line = FALSE;
+ } else if (*a_char != '\n') {
+ PRIVATE (a_this)->col++;
+ }
+
+ if (*a_char == '\n') {
+ PRIVATE (a_this)->end_of_line = TRUE;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_set_line_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_line_num: the new line number.
+ *
+ *Setter of the current line number.
+ *
+ *Return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_line_num (CRInput * a_this, glong a_line_num)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->line = a_line_num;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_line_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_line_num: the returned line number.
+ *
+ *Getter of the current line number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_line_num (CRInput const * a_this, glong * a_line_num)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_line_num, CR_BAD_PARAM_ERROR);
+
+ *a_line_num = PRIVATE (a_this)->line;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_column_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_col: the new column number.
+ *
+ *Setter of the current column number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_column_num (CRInput * a_this, glong a_col)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->col = a_col;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_column_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_col: out parameter
+ *
+ *Getter of the current column number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_column_num (CRInput const * a_this, glong * a_col)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_col,
+ CR_BAD_PARAM_ERROR);
+
+ *a_col = PRIVATE (a_this)->col;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_increment_line_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_increment: the increment to add to the line number.
+ *
+ *Increments the current line number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_increment_line_num (CRInput * a_this, glong a_increment)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->line += a_increment;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_increment_col_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_increment: the increment to add to the column number.
+ *
+ *Increments the current column number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_increment_col_num (CRInput * a_this, glong a_increment)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->col += a_increment;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_consume_char:
+ *@a_this: the this pointer.
+ *@a_char: the character to consume. If set to zero,
+ *consumes any character.
+ *
+ *Consumes the next character of the input stream if
+ *and only if that character equals a_char.
+ *
+ *Returns CR_OK upon successful completion, CR_PARSING_ERROR if
+ *next char is different from a_char, an other error code otherwise
+ */
+enum CRStatus
+cr_input_consume_char (CRInput * a_this, guint32 a_char)
+{
+ guint32 c;
+ enum CRStatus status;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if ((status = cr_input_peek_char (a_this, &c)) != CR_OK) {
+ return status;
+ }
+
+ if (c == a_char || a_char == 0) {
+ status = cr_input_read_char (a_this, &c);
+ } else {
+ return CR_PARSING_ERROR;
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_consume_chars:
+ *@a_this: the this pointer of the current instance of #CRInput.
+ *@a_char: the character to consume.
+ *@a_nb_char: in/out parameter. The number of characters to consume.
+ *If set to a negative value, the function will consume all the occurrences
+ *of a_char found.
+ *After return, if the return value equals CR_OK, this variable contains
+ *the number of characters actually consumed.
+ *
+ *Consumes up to a_nb_char occurrences of the next contiguous characters
+ *which equal a_char. Note that the next character of the input stream
+ **MUST* equal a_char to trigger the consumption, or else, the error
+ *code CR_PARSING_ERROR is returned.
+ *If the number of contiguous characters that equals a_char is less than
+ *a_nb_char, then this function consumes all the characters it can consume.
+ *
+ *Returns CR_OK if at least one character has been consumed, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_consume_chars (CRInput * a_this, guint32 a_char, gulong * a_nb_char)
+{
+ enum CRStatus status = CR_OK;
+ gulong nb_consumed = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_char,
+ CR_BAD_PARAM_ERROR);
+
+ g_return_val_if_fail (a_char != 0 || a_nb_char != NULL,
+ CR_BAD_PARAM_ERROR);
+
+ for (nb_consumed = 0; ((status == CR_OK)
+ && (*a_nb_char > 0
+ && nb_consumed < *a_nb_char));
+ nb_consumed++) {
+ status = cr_input_consume_char (a_this, a_char);
+ }
+
+ *a_nb_char = nb_consumed;
+
+ if ((nb_consumed > 0)
+ && ((status == CR_PARSING_ERROR)
+ || (status == CR_END_OF_INPUT_ERROR))) {
+ status = CR_OK;
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_consume_white_spaces:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_nb_chars: in/out parameter. The number of white spaces to
+ *consume. After return, holds the number of white spaces actually consumed.
+ *
+ *Same as cr_input_consume_chars() but this one consumes white
+ *spaces.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_consume_white_spaces (CRInput * a_this, gulong * a_nb_chars)
+{
+ enum CRStatus status = CR_OK;
+ guint32 cur_char = 0,
+ nb_consumed = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_chars,
+ CR_BAD_PARAM_ERROR);
+
+ for (nb_consumed = 0;
+ ((*a_nb_chars > 0) && (nb_consumed < *a_nb_chars));
+ nb_consumed++) {
+ status = cr_input_peek_char (a_this, &cur_char);
+ if (status != CR_OK)
+ break;
+
+ /*if the next char is a white space, consume it ! */
+ if (cr_utils_is_white_space (cur_char) == TRUE) {
+ status = cr_input_read_char (a_this, &cur_char);
+ if (status != CR_OK)
+ break;
+ continue;
+ }
+
+ break;
+
+ }
+
+ *a_nb_chars = (gulong) nb_consumed;
+
+ if (nb_consumed && status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_peek_char:
+ *@a_this: the current instance of #CRInput.
+ *@a_char: out parameter. The returned character.
+ *
+ *Same as cr_input_read_char() but does not update the
+ *internal state of the input stream. The next call
+ *to cr_input_peek_char() or cr_input_read_char() will thus
+ *return the same character as the current one.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_peek_char (CRInput const * a_this, guint32 * a_char)
+{
+ enum CRStatus status = CR_OK;
+ gulong consumed = 0,
+ nb_bytes_left = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->next_byte_index >=
+ PRIVATE (a_this)->in_buf_size) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ nb_bytes_left = cr_input_get_nb_bytes_left (a_this);
+
+ if (nb_bytes_left < 1) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ status = cr_utils_read_char_from_utf8_buf
+ (PRIVATE (a_this)->in_buf +
+ PRIVATE (a_this)->next_byte_index,
+ nb_bytes_left, a_char, &consumed);
+
+ return status;
+}
+
+/**
+ * cr_input_peek_byte:
+ *@a_this: the current instance of #CRInput.
+ *@a_origin: the origin to consider in the calculation
+ *of the position of the byte to peek.
+ *@a_offset: the offset of the byte to peek, starting from
+ *the origin specified by a_origin.
+ *@a_byte: out parameter the peeked byte.
+ *
+ *Gets a byte from the input stream,
+ *starting from the current position in the input stream.
+ *Unlike cr_input_peek_next_byte() this method
+ *does not update the state of the current input stream.
+ *Subsequent calls to cr_input_peek_byte with the same arguments
+ *will return the same byte.
+ *
+ *Returns CR_OK upon successful completion or,
+ *CR_BAD_PARAM_ERROR if at least one of the parameters is invalid;
+ *CR_OUT_OF_BOUNDS_ERROR if the indexed byte is out of bounds.
+ */
+enum CRStatus
+cr_input_peek_byte (CRInput const * a_this, enum CRSeekPos a_origin,
+ gulong a_offset, guchar * a_byte)
+{
+ gulong abs_offset = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_byte, CR_BAD_PARAM_ERROR);
+
+ switch (a_origin) {
+
+ case CR_SEEK_CUR:
+ abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_offset;
+ break;
+
+ case CR_SEEK_BEGIN:
+ abs_offset = a_offset;
+ break;
+
+ case CR_SEEK_END:
+ abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_offset;
+ break;
+
+ default:
+ return CR_BAD_PARAM_ERROR;
+ }
+
+ if (abs_offset < PRIVATE (a_this)->in_buf_size) {
+
+ *a_byte = PRIVATE (a_this)->in_buf[abs_offset];
+
+ return CR_OK;
+
+ } else {
+ return CR_END_OF_INPUT_ERROR;
+ }
+}
+
+/**
+ * cr_input_peek_byte2:
+ *@a_this: the current byte input stream.
+ *@a_offset: the offset of the byte to peek, starting
+ *from the current input position pointer.
+ *@a_eof: out parameter. Is set to true is we reach end of
+ *stream. If set to NULL by the caller, this parameter is not taken
+ *in account.
+ *
+ *Same as cr_input_peek_byte() but with a simplified
+ *interface.
+ *
+ *Returns the read byte or 0 if something bad happened.
+ */
+guchar
+cr_input_peek_byte2 (CRInput const * a_this, gulong a_offset, gboolean * a_eof)
+{
+ guchar result = 0;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), 0);
+
+ if (a_eof)
+ *a_eof = FALSE;
+
+ status = cr_input_peek_byte (a_this, CR_SEEK_CUR, a_offset, &result);
+
+ if ((status == CR_END_OF_INPUT_ERROR)
+ && a_eof)
+ *a_eof = TRUE;
+
+ return result;
+}
+
+/**
+ * cr_input_get_byte_addr:
+ *@a_this: the current instance of #CRInput.
+ *@a_offset: the offset of the byte in the input stream starting
+ *from the beginning of the stream.
+ *
+ *Gets the memory address of the byte located at a given offset
+ *in the input stream.
+ *
+ *Returns the address, otherwise NULL if an error occurred.
+ */
+guchar *
+cr_input_get_byte_addr (CRInput * a_this, gulong a_offset)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), NULL);
+
+ if (a_offset >= PRIVATE (a_this)->nb_bytes) {
+ return NULL;
+ }
+
+ return &PRIVATE (a_this)->in_buf[a_offset];
+}
+
+/**
+ * cr_input_get_cur_byte_addr:
+ *@a_this: the current input stream
+ *@a_offset: out parameter. The returned address.
+ *
+ *Gets the address of the current character pointer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_cur_byte_addr (CRInput * a_this, guchar ** a_offset)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_offset,
+ CR_BAD_PARAM_ERROR);
+
+ if (!PRIVATE (a_this)->next_byte_index) {
+ return CR_START_OF_INPUT_ERROR;
+ }
+
+ *a_offset = cr_input_get_byte_addr
+ (a_this, PRIVATE (a_this)->next_byte_index - 1);
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_seek_index:
+ *@a_this: the current instance of #CRInput.
+ *@a_origin: the origin to consider during the calculation
+ *of the absolute position of the new "current byte index".
+ *@a_pos: the relative offset of the new "current byte index."
+ *This offset is relative to the origin a_origin.
+ *
+ *Sets the "current byte index" of the current instance
+ *of #CRInput. Next call to cr_input_get_byte() will return
+ *the byte next after the new "current byte index".
+ *
+ *Returns CR_OK upon successful completion otherwise returns
+ *CR_BAD_PARAM_ERROR if at least one of the parameters is not valid
+ *or CR_OUT_BOUNDS_ERROR in case of error.
+ */
+enum CRStatus
+cr_input_seek_index (CRInput * a_this, enum CRSeekPos a_origin, gint a_pos)
+{
+
+ glong abs_offset = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ switch (a_origin) {
+
+ case CR_SEEK_CUR:
+ abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_pos;
+ break;
+
+ case CR_SEEK_BEGIN:
+ abs_offset = a_pos;
+ break;
+
+ case CR_SEEK_END:
+ abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_pos;
+ break;
+
+ default:
+ return CR_BAD_PARAM_ERROR;
+ }
+
+ if ((abs_offset > 0)
+ && (gulong) abs_offset < PRIVATE (a_this)->nb_bytes) {
+
+ /*update the input stream's internal state */
+ PRIVATE (a_this)->next_byte_index = abs_offset + 1;
+
+ return CR_OK;
+ }
+
+ return CR_OUT_OF_BOUNDS_ERROR;
+}
+
+/**
+ * cr_input_get_cur_pos:
+ *@a_this: the current instance of #CRInput.
+ *@a_pos: out parameter. The returned position.
+ *
+ *Gets the position of the "current byte index" which
+ *is basically the position of the last returned byte in the
+ *input stream.
+ *
+ *Returns CR_OK upon successful completion. Otherwise,
+ *CR_BAD_PARAMETER_ERROR if at least one of the arguments is invalid.
+ *CR_START_OF_INPUT if no call to either cr_input_read_byte()
+ *or cr_input_seek_index() have been issued before calling
+ *cr_input_get_cur_pos()
+ *Note that the out parameters of this function are valid if and only if this
+ *function returns CR_OK.
+ */
+enum CRStatus
+cr_input_get_cur_pos (CRInput const * a_this, CRInputPos * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos,
+ CR_BAD_PARAM_ERROR);
+
+ a_pos->next_byte_index = PRIVATE (a_this)->next_byte_index;
+ a_pos->line = PRIVATE (a_this)->line;
+ a_pos->col = PRIVATE (a_this)->col;
+ a_pos->end_of_line = PRIVATE (a_this)->end_of_line;
+ a_pos->end_of_file = PRIVATE (a_this)->end_of_input;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_parsing_location:
+ *@a_this: the current instance of #CRInput
+ *@a_loc: the set parsing location.
+ *
+ *Gets the current parsing location.
+ *The Parsing location is a public datastructure that
+ *represents the current line/column/byte offset/ in the input
+ *stream.
+ *
+ *Returns CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_input_get_parsing_location (CRInput const *a_this,
+ CRParsingLocation *a_loc)
+{
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_loc,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_loc->line = PRIVATE (a_this)->line ;
+ a_loc->column = PRIVATE (a_this)->col ;
+ if (PRIVATE (a_this)->next_byte_index) {
+ a_loc->byte_offset = PRIVATE (a_this)->next_byte_index - 1 ;
+ } else {
+ a_loc->byte_offset = PRIVATE (a_this)->next_byte_index ;
+ }
+ return CR_OK ;
+}
+
+/**
+ * cr_input_get_cur_index:
+ *@a_this: the "this pointer" of the current instance of
+ *#CRInput
+ *@a_index: out parameter. The returned index.
+ *
+ *Getter of the next byte index.
+ *It actually returns the index of the
+ *next byte to be read.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_get_cur_index (CRInput const * a_this, glong * a_index)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_index, CR_BAD_PARAM_ERROR);
+
+ *a_index = PRIVATE (a_this)->next_byte_index;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_cur_index:
+ *@a_this: the "this pointer" of the current instance
+ *of #CRInput .
+ *@a_index: the new index to set.
+ *
+ *Setter of the next byte index.
+ *It sets the index of the next byte to be read.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_cur_index (CRInput * a_this, glong a_index)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->next_byte_index = a_index;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_end_of_file:
+ *@a_this: the current instance of #CRInput.
+ *@a_eof: the new end of file flag.
+ *
+ *Sets the end of file flag.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_end_of_file (CRInput * a_this, gboolean a_eof)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->end_of_input = a_eof;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_end_of_file:
+ *@a_this: the current instance of #CRInput.
+ *@a_eof: out parameter the place to put the end of
+ *file flag.
+ *
+ *Gets the end of file flag.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_end_of_file (CRInput const * a_this, gboolean * a_eof)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_eof, CR_BAD_PARAM_ERROR);
+
+ *a_eof = PRIVATE (a_this)->end_of_input;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_end_of_line:
+ *@a_this: the current instance of #CRInput.
+ *@a_eol: the new end of line flag.
+ *
+ *Sets the end of line flag.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_set_end_of_line (CRInput * a_this, gboolean a_eol)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->end_of_line = a_eol;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_end_of_line:
+ *@a_this: the current instance of #CRInput
+ *@a_eol: out parameter. The place to put
+ *the returned flag
+ *
+ *Gets the end of line flag of the current input.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_get_end_of_line (CRInput const * a_this, gboolean * a_eol)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_eol, CR_BAD_PARAM_ERROR);
+
+ *a_eol = PRIVATE (a_this)->end_of_line;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_cur_pos:
+ *@a_this: the "this pointer" of the current instance of
+ *#CRInput.
+ *@a_pos: the new position.
+ *
+ *Sets the current position in the input stream.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_cur_pos (CRInput * a_this, CRInputPos const * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos,
+ CR_BAD_PARAM_ERROR);
+
+ cr_input_set_column_num (a_this, a_pos->col);
+ cr_input_set_line_num (a_this, a_pos->line);
+ cr_input_set_cur_index (a_this, a_pos->next_byte_index);
+ cr_input_set_end_of_line (a_this, a_pos->end_of_line);
+ cr_input_set_end_of_file (a_this, a_pos->end_of_file);
+
+ return CR_OK;
+}
diff --git a/src/st/croco/cr-input.h b/src/st/croco/cr-input.h
new file mode 100644
index 0000000..9eb402a
--- /dev/null
+++ b/src/st/croco/cr-input.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset:8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See the COPYRIGHTS file for copyrights information.
+ */
+
+#ifndef __CR_INPUT_SRC_H__
+#define __CR_INPUT_SRC_H__
+
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The libcroco basic input stream class
+ *declaration file.
+ */
+
+typedef struct _CRInput CRInput ;
+typedef struct _CRInputPriv CRInputPriv ;
+
+/**
+ *The #CRInput class provides the abstraction of
+ *an utf8-encoded character stream.
+ */
+struct _CRInput
+{
+ CRInputPriv *priv ;
+} ;
+
+typedef struct _CRInputPos CRInputPos ;
+
+struct _CRInputPos
+{
+ glong line ;
+ glong col ;
+ gboolean end_of_file ;
+ gboolean end_of_line ;
+ glong next_byte_index ;
+} ;
+
+CRInput *
+cr_input_new_from_buf (guchar *a_buf, gulong a_len,
+ enum CREncoding a_enc, gboolean a_free_buf) ;
+CRInput *
+cr_input_new_from_uri (const gchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+void
+cr_input_destroy (CRInput *a_this) ;
+
+void
+cr_input_ref (CRInput *a_this) ;
+
+gboolean
+cr_input_unref (CRInput *a_this) ;
+
+enum CRStatus
+cr_input_read_byte (CRInput *a_this, guchar *a_byte) ;
+
+enum CRStatus
+cr_input_read_char (CRInput *a_this, guint32 *a_char) ;
+
+enum CRStatus
+cr_input_consume_chars (CRInput *a_this, guint32 a_char,
+ gulong *a_nb_char) ;
+
+enum CRStatus
+cr_input_consume_char (CRInput *a_this, guint32 a_char) ;
+
+enum CRStatus
+cr_input_consume_white_spaces (CRInput *a_this, gulong *a_nb_chars) ;
+
+enum CRStatus
+cr_input_peek_byte (CRInput const *a_this, enum CRSeekPos a_origin,
+ gulong a_offset, guchar *a_byte) ;
+
+guchar
+cr_input_peek_byte2 (CRInput const *a_this, gulong a_offset,
+ gboolean *a_eof) ;
+
+enum CRStatus
+cr_input_peek_char (CRInput const *a_this, guint32 *a_char) ;
+
+guchar *
+cr_input_get_byte_addr (CRInput *a_this,
+ gulong a_offset) ;
+
+enum CRStatus
+cr_input_get_cur_byte_addr (CRInput *a_this, guchar ** a_offset) ;
+
+enum CRStatus
+cr_input_seek_index (CRInput *a_this,
+ enum CRSeekPos a_origin, gint a_pos) ;
+
+enum CRStatus
+cr_input_get_cur_index (CRInput const *a_this, glong *a_index) ;
+
+enum CRStatus
+cr_input_set_cur_index (CRInput *a_this, glong a_index) ;
+
+enum CRStatus
+cr_input_get_cur_pos (CRInput const *a_this, CRInputPos * a_pos) ;
+
+enum CRStatus
+cr_input_set_cur_pos (CRInput *a_this, CRInputPos const *a_pos) ;
+
+enum CRStatus
+cr_input_get_parsing_location (CRInput const *a_this,
+ CRParsingLocation *a_loc) ;
+
+enum CRStatus
+cr_input_get_end_of_line (CRInput const *a_this, gboolean *a_eol) ;
+
+enum CRStatus
+cr_input_set_end_of_line (CRInput *a_this, gboolean a_eol) ;
+
+enum CRStatus
+cr_input_get_end_of_file (CRInput const *a_this, gboolean *a_eof) ;
+
+enum CRStatus
+cr_input_set_end_of_file (CRInput *a_this, gboolean a_eof) ;
+
+enum CRStatus
+cr_input_set_line_num (CRInput *a_this, glong a_line_num) ;
+
+enum CRStatus
+cr_input_get_line_num (CRInput const *a_this, glong *a_line_num) ;
+
+enum CRStatus
+cr_input_set_column_num (CRInput *a_this, glong a_col) ;
+
+enum CRStatus
+cr_input_get_column_num (CRInput const *a_this, glong *a_col) ;
+
+enum CRStatus
+cr_input_increment_line_num (CRInput *a_this,
+ glong a_increment) ;
+
+enum CRStatus
+cr_input_increment_col_num (CRInput *a_this,
+ glong a_increment) ;
+
+glong
+cr_input_get_nb_bytes_left (CRInput const *a_this) ;
+
+enum CRStatus
+cr_input_end_of_input (CRInput const *a_this, gboolean *a_end_of_input) ;
+
+G_END_DECLS
+
+#endif /*__CR_INPUT_SRC_H__*/
+
diff --git a/src/st/croco/cr-num.c b/src/st/croco/cr-num.c
new file mode 100644
index 0000000..ba17285
--- /dev/null
+++ b/src/st/croco/cr-num.c
@@ -0,0 +1,313 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+/**
+ *@CRNum:
+ *
+ *The definition
+ *of the #CRNum class.
+ */
+
+#include "cr-num.h"
+#include "string.h"
+
+/**
+ * cr_num_new:
+ *
+ *#CRNum.
+ *
+ *Returns the newly built instance of
+ *#CRNum.
+ */
+CRNum *
+cr_num_new (void)
+{
+ CRNum *result = NULL;
+
+ result = g_try_malloc (sizeof (CRNum));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRNum));
+
+ return result;
+}
+
+/**
+ * cr_num_new_with_val:
+ * @a_val: the numerical value of the number.
+ * @a_type: the type of number.
+ *
+ * A constructor of #CRNum.
+ *
+ * Returns the newly built instance of #CRNum or
+ * NULL if an error arises.
+ */
+CRNum *
+cr_num_new_with_val (gdouble a_val, enum CRNumType a_type)
+{
+ CRNum *result = NULL;
+
+ result = cr_num_new ();
+
+ g_return_val_if_fail (result, NULL);
+
+ result->val = a_val;
+ result->type = a_type;
+
+ return result;
+}
+
+/**
+ * cr_num_to_string:
+ *@a_this: the current instance of #CRNum.
+ *
+ *Returns the newly built string representation
+ *of the current instance of #CRNum. The returned
+ *string is NULL terminated. The caller *must*
+ *free the returned string.
+ */
+guchar *
+cr_num_to_string (CRNum const * a_this)
+{
+ gdouble test_val = 0.0;
+
+ guchar *tmp_char1 = NULL,
+ *tmp_char2 = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ test_val = a_this->val - (glong) a_this->val;
+
+ if (!test_val) {
+ tmp_char1 = (guchar *) g_strdup_printf ("%ld", (glong) a_this->val);
+ } else {
+ tmp_char1 = (guchar *) g_new0 (char, G_ASCII_DTOSTR_BUF_SIZE + 1);
+ if (tmp_char1 != NULL)
+ g_ascii_dtostr ((gchar *) tmp_char1, G_ASCII_DTOSTR_BUF_SIZE, a_this->val);
+ }
+
+ g_return_val_if_fail (tmp_char1, NULL);
+
+ switch (a_this->type) {
+ case NUM_LENGTH_EM:
+ tmp_char2 = (guchar *) "em";
+ break;
+
+ case NUM_LENGTH_EX:
+ tmp_char2 = (guchar *) "ex";
+ break;
+
+ case NUM_LENGTH_PX:
+ tmp_char2 = (guchar *) "px";
+ break;
+
+ case NUM_LENGTH_IN:
+ tmp_char2 = (guchar *) "in";
+ break;
+
+ case NUM_LENGTH_CM:
+ tmp_char2 = (guchar *) "cm";
+ break;
+
+ case NUM_LENGTH_MM:
+ tmp_char2 = (guchar *) "mm";
+ break;
+
+ case NUM_LENGTH_PT:
+ tmp_char2 = (guchar *) "pt";
+ break;
+
+ case NUM_LENGTH_PC:
+ tmp_char2 = (guchar *) "pc";
+ break;
+
+ case NUM_ANGLE_DEG:
+ tmp_char2 = (guchar *) "deg";
+ break;
+
+ case NUM_ANGLE_RAD:
+ tmp_char2 = (guchar *) "rad";
+ break;
+
+ case NUM_ANGLE_GRAD:
+ tmp_char2 = (guchar *) "grad";
+ break;
+
+ case NUM_TIME_MS:
+ tmp_char2 = (guchar *) "ms";
+ break;
+
+ case NUM_TIME_S:
+ tmp_char2 = (guchar *) "s";
+ break;
+
+ case NUM_FREQ_HZ:
+ tmp_char2 = (guchar *) "Hz";
+ break;
+
+ case NUM_FREQ_KHZ:
+ tmp_char2 = (guchar *) "KHz";
+ break;
+
+ case NUM_PERCENTAGE:
+ tmp_char2 = (guchar *) "%";
+ break;
+ case NUM_INHERIT:
+ tmp_char2 = (guchar *) "inherit";
+ break ;
+ case NUM_AUTO:
+ tmp_char2 = (guchar *) "auto";
+ break ;
+ case NUM_GENERIC:
+ tmp_char2 = NULL ;
+ break ;
+ default:
+ tmp_char2 = (guchar *) "unknown";
+ break;
+ }
+
+ if (tmp_char2) {
+ result = (guchar *) g_strconcat ((gchar *) tmp_char1, tmp_char2, NULL);
+ g_free (tmp_char1);
+ } else {
+ result = tmp_char1;
+ }
+
+ return result;
+}
+
+/**
+ * cr_num_copy:
+ *@a_src: the instance of #CRNum to copy.
+ *Must be non NULL.
+ *@a_dest: the destination of the copy.
+ *Must be non NULL
+ *
+ *Copies an instance of #CRNum.
+ *
+ *Returns CR_OK upon successful completion, an
+ *error code otherwise.
+ */
+enum CRStatus
+cr_num_copy (CRNum * a_dest, CRNum const * a_src)
+{
+ g_return_val_if_fail (a_dest && a_src, CR_BAD_PARAM_ERROR);
+
+ memcpy (a_dest, a_src, sizeof (CRNum));
+
+ return CR_OK;
+}
+
+/**
+ * cr_num_dup:
+ *@a_this: the instance of #CRNum to duplicate.
+ *
+ *Duplicates an instance of #CRNum
+ *
+ *Returns the newly created (duplicated) instance of #CRNum.
+ *Must be freed by cr_num_destroy().
+ */
+CRNum *
+cr_num_dup (CRNum const * a_this)
+{
+ CRNum *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ result = cr_num_new ();
+ g_return_val_if_fail (result, NULL);
+
+ status = cr_num_copy (result, a_this);
+ g_return_val_if_fail (status == CR_OK, NULL);
+
+ return result;
+}
+
+/**
+ * cr_num_set:
+ *Sets an instance of #CRNum.
+ *@a_this: the current instance of #CRNum to be set.
+ *@a_val: the new numerical value to be hold by the current
+ *instance of #CRNum
+ *@a_type: the new type of #CRNum.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_num_set (CRNum * a_this, gdouble a_val, enum CRNumType a_type)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ a_this->val = a_val;
+ a_this->type = a_type;
+
+ return CR_OK;
+}
+
+/**
+ * cr_num_is_fixed_length:
+ * @a_this: the current instance of #CRNum .
+ *
+ *Tests if the current instance of #CRNum is a fixed
+ *length value or not. Typically a fixed length value
+ *is anything from NUM_LENGTH_EM to NUM_LENGTH_PC.
+ *See the definition of #CRNumType to see what we mean.
+ *
+ *Returns TRUE if the instance of #CRNum is a fixed length number,
+ *FALSE otherwise.
+ */
+gboolean
+cr_num_is_fixed_length (CRNum const * a_this)
+{
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->type >= NUM_LENGTH_EM
+ && a_this->type <= NUM_LENGTH_PC) {
+ result = TRUE ;
+ }
+ return result ;
+}
+
+/**
+ * cr_num_destroy:
+ *@a_this: the this pointer of
+ *the current instance of #CRNum.
+ *
+ *The destructor of #CRNum.
+ */
+void
+cr_num_destroy (CRNum * a_this)
+{
+ g_return_if_fail (a_this);
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-num.h b/src/st/croco/cr-num.h
new file mode 100644
index 0000000..2b73aaf
--- /dev/null
+++ b/src/st/croco/cr-num.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information
+ */
+
+
+/**
+ *@file
+ *The declaration
+ *of the #CRNum class.
+ */
+
+#ifndef __CR_NUM_H__
+#define __CR_NUM_H__
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the #CRNum class.
+ *
+ */
+
+/**
+ *The different types
+ *of numbers.
+ *Please, do not modify
+ *the declaration order of the enum
+ *members, unless you know
+ *what you are doing.
+ */
+enum CRNumType
+{
+ NUM_AUTO = 0,
+ NUM_GENERIC,
+ NUM_LENGTH_EM,
+ NUM_LENGTH_EX,
+ NUM_LENGTH_PX,
+ NUM_LENGTH_IN,
+ NUM_LENGTH_CM,
+ NUM_LENGTH_MM,
+ NUM_LENGTH_PT,
+ NUM_LENGTH_PC,
+ NUM_ANGLE_DEG,
+ NUM_ANGLE_RAD,
+ NUM_ANGLE_GRAD,
+ NUM_TIME_MS,
+ NUM_TIME_S,
+ NUM_FREQ_HZ,
+ NUM_FREQ_KHZ,
+ NUM_PERCENTAGE,
+ NUM_INHERIT,
+ NUM_UNKNOWN_TYPE,
+ NB_NUM_TYPE
+} ;
+
+
+/**
+ *An abstraction of a number (num)
+ *as defined in the css2 spec.
+ */
+typedef struct _CRNum CRNum ;
+
+/**
+ *An abstraction of a number (num)
+ *as defined in the css2 spec.
+ */
+struct _CRNum
+{
+ enum CRNumType type ;
+ gdouble val ;
+ CRParsingLocation location ;
+} ;
+
+CRNum *
+cr_num_new (void) ;
+
+CRNum *
+cr_num_new_with_val (gdouble a_val,
+ enum CRNumType a_type) ;
+
+CRNum *
+cr_num_dup (CRNum const *a_this) ;
+
+guchar *
+cr_num_to_string (CRNum const *a_this) ;
+
+enum CRStatus
+cr_num_copy (CRNum *a_dest, CRNum const *a_src) ;
+
+enum CRStatus
+cr_num_set (CRNum *a_this, gdouble a_val,
+ enum CRNumType a_type) ;
+
+gboolean
+cr_num_is_fixed_length (CRNum const *a_this) ;
+
+void
+cr_num_destroy (CRNum *a_this) ;
+
+
+G_END_DECLS
+
+
+#endif /*__CR_NUM_H__*/
diff --git a/src/st/croco/cr-om-parser.c b/src/st/croco/cr-om-parser.c
new file mode 100644
index 0000000..90f7106
--- /dev/null
+++ b/src/st/croco/cr-om-parser.c
@@ -0,0 +1,1142 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-utils.h"
+#include "cr-om-parser.h"
+
+/**
+ *@CROMParser:
+ *
+ *The definition of the CSS Object Model Parser.
+ *This parser uses (and sits) the SAC api of libcroco defined
+ *in cr-parser.h and cr-doc-handler.h
+ */
+
+struct _CROMParserPriv {
+ CRParser *parser;
+};
+
+#define PRIVATE(a_this) ((a_this)->priv)
+
+/*
+ *Forward declaration of a type defined later
+ *in this file.
+ */
+struct _ParsingContext;
+typedef struct _ParsingContext ParsingContext;
+
+static ParsingContext *new_parsing_context (void);
+
+static void destroy_context (ParsingContext * a_ctxt);
+
+static void unrecoverable_error (CRDocHandler * a_this);
+
+static void error (CRDocHandler * a_this);
+
+static void property (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_expression,
+ gboolean a_important);
+
+static void end_selector (CRDocHandler * a_this,
+ CRSelector * a_selector_list);
+
+static void start_selector (CRDocHandler * a_this,
+ CRSelector * a_selector_list);
+
+static void start_font_face (CRDocHandler * a_this,
+ CRParsingLocation *a_location);
+
+static void end_font_face (CRDocHandler * a_this);
+
+static void end_document (CRDocHandler * a_this);
+
+static void start_document (CRDocHandler * a_this);
+
+static void charset (CRDocHandler * a_this,
+ CRString * a_charset,
+ CRParsingLocation *a_location);
+
+static void start_page (CRDocHandler * a_this, CRString * a_page,
+ CRString * a_pseudo_page,
+ CRParsingLocation *a_location);
+
+static void end_page (CRDocHandler * a_this, CRString * a_page,
+ CRString * a_pseudo_page);
+
+static void start_media (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRParsingLocation *a_location);
+
+static void end_media (CRDocHandler * a_this,
+ GList * a_media_list);
+
+static void import_style (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRString * a_uri,
+ CRString * a_uri_default_ns,
+ CRParsingLocation *a_location);
+
+struct _ParsingContext {
+ CRStyleSheet *stylesheet;
+ CRStatement *cur_stmt;
+ CRStatement *cur_media_stmt;
+};
+
+/********************************************
+ *Private methods
+ ********************************************/
+
+static ParsingContext *
+new_parsing_context (void)
+{
+ ParsingContext *result = NULL;
+
+ result = g_try_malloc (sizeof (ParsingContext));
+ if (!result) {
+ cr_utils_trace_info ("Out of Memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (ParsingContext));
+ return result;
+}
+
+static void
+destroy_context (ParsingContext * a_ctxt)
+{
+ g_return_if_fail (a_ctxt);
+
+ if (a_ctxt->stylesheet) {
+ cr_stylesheet_destroy (a_ctxt->stylesheet);
+ a_ctxt->stylesheet = NULL;
+ }
+ if (a_ctxt->cur_stmt) {
+ cr_statement_destroy (a_ctxt->cur_stmt);
+ a_ctxt->cur_stmt = NULL;
+ }
+ g_free (a_ctxt);
+}
+
+static enum CRStatus
+cr_om_parser_init_default_sac_handler (CROMParser * a_this)
+{
+ CRDocHandler *sac_handler = NULL;
+ gboolean created_handler = FALSE;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->parser,
+ CR_BAD_PARAM_ERROR);
+
+ status = cr_parser_get_sac_handler (PRIVATE (a_this)->parser,
+ &sac_handler);
+ g_return_val_if_fail (status == CR_OK, status);
+
+ if (!sac_handler) {
+ sac_handler = cr_doc_handler_new ();
+ created_handler = TRUE;
+ }
+
+ /*
+ *initialize here the sac handler.
+ */
+ sac_handler->start_document = start_document;
+ sac_handler->end_document = end_document;
+ sac_handler->start_selector = start_selector;
+ sac_handler->end_selector = end_selector;
+ sac_handler->property = property;
+ sac_handler->start_font_face = start_font_face;
+ sac_handler->end_font_face = end_font_face;
+ sac_handler->error = error;
+ sac_handler->unrecoverable_error = unrecoverable_error;
+ sac_handler->charset = charset;
+ sac_handler->start_page = start_page;
+ sac_handler->end_page = end_page;
+ sac_handler->start_media = start_media;
+ sac_handler->end_media = end_media;
+ sac_handler->import_style = import_style;
+
+ if (created_handler) {
+ status = cr_parser_set_sac_handler (PRIVATE (a_this)->parser,
+ sac_handler);
+ cr_doc_handler_unref (sac_handler);
+ }
+
+ return status;
+
+}
+
+static void
+start_document (CRDocHandler * a_this)
+{
+ ParsingContext *ctxt = NULL;
+ CRStyleSheet *stylesheet = NULL;
+
+ g_return_if_fail (a_this);
+
+ ctxt = new_parsing_context ();
+ g_return_if_fail (ctxt);
+
+ stylesheet = cr_stylesheet_new (NULL);
+ ctxt->stylesheet = stylesheet;
+ cr_doc_handler_set_ctxt (a_this, ctxt);
+}
+
+static void
+start_font_face (CRDocHandler * a_this,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail (ctxt->cur_stmt == NULL);
+
+ ctxt->cur_stmt =
+ cr_statement_new_at_font_face_rule (ctxt->stylesheet, NULL);
+
+ g_return_if_fail (ctxt->cur_stmt);
+}
+
+static void
+end_font_face (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRStatement *stmts = NULL;
+
+ g_return_if_fail (a_this);
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail
+ (ctxt->cur_stmt
+ && ctxt->cur_stmt->type == AT_FONT_FACE_RULE_STMT
+ && ctxt->stylesheet);
+
+ stmts = cr_statement_append (ctxt->stylesheet->statements,
+ ctxt->cur_stmt);
+ if (!stmts)
+ goto error;
+
+ ctxt->stylesheet->statements = stmts;
+ stmts = NULL;
+ ctxt->cur_stmt = NULL;
+
+ return;
+
+ error:
+
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+
+ if (!stmts) {
+ cr_statement_destroy (stmts);
+ stmts = NULL;
+ }
+}
+
+static void
+end_document (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ if (!ctxt->stylesheet || ctxt->cur_stmt)
+ goto error;
+
+ status = cr_doc_handler_set_result (a_this, ctxt->stylesheet);
+ g_return_if_fail (status == CR_OK);
+
+ ctxt->stylesheet = NULL;
+ destroy_context (ctxt);
+ cr_doc_handler_set_ctxt (a_this, NULL);
+
+ return;
+
+ error:
+ if (ctxt) {
+ destroy_context (ctxt);
+ }
+}
+
+static void
+charset (CRDocHandler * a_this, CRString * a_charset,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *stmt = NULL,
+ *stmt2 = NULL;
+ CRString *charset = NULL;
+
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail (ctxt->stylesheet);
+
+ charset = cr_string_dup (a_charset) ;
+ stmt = cr_statement_new_at_charset_rule (ctxt->stylesheet, charset);
+ g_return_if_fail (stmt);
+ stmt2 = cr_statement_append (ctxt->stylesheet->statements, stmt);
+ if (!stmt2) {
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ }
+ if (charset) {
+ cr_string_destroy (charset);
+ }
+ return;
+ }
+ ctxt->stylesheet->statements = stmt2;
+ stmt2 = NULL;
+}
+
+static void
+start_page (CRDocHandler * a_this,
+ CRString * a_page,
+ CRString * a_pseudo,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail (ctxt->cur_stmt == NULL);
+
+ ctxt->cur_stmt = cr_statement_new_at_page_rule
+ (ctxt->stylesheet, NULL, NULL, NULL);
+ if (a_page) {
+ ctxt->cur_stmt->kind.page_rule->name =
+ cr_string_dup (a_page) ;
+
+ if (!ctxt->cur_stmt->kind.page_rule->name) {
+ goto error;
+ }
+ }
+ if (a_pseudo) {
+ ctxt->cur_stmt->kind.page_rule->pseudo =
+ cr_string_dup (a_pseudo) ;
+ if (!ctxt->cur_stmt->kind.page_rule->pseudo) {
+ goto error;
+ }
+ }
+ return;
+
+ error:
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+}
+
+static void
+end_page (CRDocHandler * a_this,
+ CRString * a_page,
+ CRString * a_pseudo_page)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRStatement *stmt = NULL;
+
+ (void) a_page;
+ (void) a_pseudo_page;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt->cur_stmt
+ && ctxt->cur_stmt->type == AT_PAGE_RULE_STMT
+ && ctxt->stylesheet);
+
+ stmt = cr_statement_append (ctxt->stylesheet->statements,
+ ctxt->cur_stmt);
+
+ if (stmt) {
+ ctxt->stylesheet->statements = stmt;
+ stmt = NULL;
+ ctxt->cur_stmt = NULL;
+ }
+
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+ a_page = NULL; /*keep compiler happy */
+ a_pseudo_page = NULL; /*keep compiler happy */
+}
+
+static void
+start_media (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ GList *media_list = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt
+ && ctxt->cur_stmt == NULL
+ && ctxt->cur_media_stmt == NULL
+ && ctxt->stylesheet);
+ if (a_media_list) {
+ /*duplicate the media_list */
+ media_list = cr_utils_dup_glist_of_cr_string
+ (a_media_list);
+ }
+ ctxt->cur_media_stmt =
+ cr_statement_new_at_media_rule
+ (ctxt->stylesheet, NULL, media_list);
+
+}
+
+static void
+end_media (CRDocHandler * a_this, GList * a_media_list)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRStatement *stmts = NULL;
+
+ (void) a_media_list;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt
+ && ctxt->cur_media_stmt
+ && ctxt->cur_media_stmt->type == AT_MEDIA_RULE_STMT
+ && ctxt->stylesheet);
+
+ stmts = cr_statement_append (ctxt->stylesheet->statements,
+ ctxt->cur_media_stmt);
+
+ if (!stmts) {
+ cr_statement_destroy (ctxt->cur_media_stmt);
+ ctxt->cur_media_stmt = NULL;
+ }
+
+ ctxt->stylesheet->statements = stmts;
+ stmts = NULL;
+
+ ctxt->cur_stmt = NULL ;
+ ctxt->cur_media_stmt = NULL ;
+ a_media_list = NULL;
+}
+
+static void
+import_style (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRString * a_uri,
+ CRString * a_uri_default_ns,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRString *uri = NULL;
+ CRStatement *stmt = NULL,
+ *stmt2 = NULL;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ GList *media_list = NULL ;
+
+ (void) a_uri_default_ns;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt->stylesheet);
+
+ uri = cr_string_dup (a_uri) ;
+
+ if (a_media_list)
+ media_list = cr_utils_dup_glist_of_cr_string (a_media_list) ;
+
+ stmt = cr_statement_new_at_import_rule
+ (ctxt->stylesheet, uri, media_list, NULL);
+
+ if (!stmt)
+ goto error;
+
+ if (ctxt->cur_stmt) {
+ stmt2 = cr_statement_append (ctxt->cur_stmt, stmt);
+ if (!stmt2)
+ goto error;
+ ctxt->cur_stmt = stmt2;
+ stmt2 = NULL;
+ stmt = NULL;
+ } else {
+ stmt2 = cr_statement_append (ctxt->stylesheet->statements,
+ stmt);
+ if (!stmt2)
+ goto error;
+ ctxt->stylesheet->statements = stmt2;
+ stmt2 = NULL;
+ stmt = NULL;
+ }
+
+ return;
+
+ error:
+ if (uri) {
+ cr_string_destroy (uri);
+ }
+
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ }
+ a_uri_default_ns = NULL; /*keep compiler happy */
+}
+
+static void
+start_selector (CRDocHandler * a_this, CRSelector * a_selector_list)
+{
+ enum CRStatus status = CR_OK ;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ if (ctxt->cur_stmt) {
+ /*hmm, this should be NULL so free it */
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+
+ ctxt->cur_stmt = cr_statement_new_ruleset
+ (ctxt->stylesheet, a_selector_list, NULL, NULL);
+}
+
+static void
+end_selector (CRDocHandler * a_this, CRSelector * a_selector_list)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ (void) a_selector_list;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt->cur_stmt && ctxt->stylesheet);
+
+ if (ctxt->cur_stmt) {
+ CRStatement *stmts = NULL;
+
+ if (ctxt->cur_media_stmt) {
+ CRAtMediaRule *media_rule = NULL;
+
+ media_rule = ctxt->cur_media_stmt->kind.media_rule;
+
+ stmts = cr_statement_append
+ (media_rule->rulesets, ctxt->cur_stmt);
+
+ if (!stmts) {
+ cr_utils_trace_info
+ ("Could not append a new statement");
+ cr_statement_destroy (media_rule->rulesets);
+ ctxt->cur_media_stmt->
+ kind.media_rule->rulesets = NULL;
+ return;
+ }
+ media_rule->rulesets = stmts;
+ ctxt->cur_stmt = NULL;
+ } else {
+ stmts = cr_statement_append
+ (ctxt->stylesheet->statements,
+ ctxt->cur_stmt);
+ if (!stmts) {
+ cr_utils_trace_info
+ ("Could not append a new statement");
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ return;
+ }
+ ctxt->stylesheet->statements = stmts;
+ ctxt->cur_stmt = NULL;
+ }
+
+ }
+
+ a_selector_list = NULL; /*keep compiler happy */
+}
+
+static void
+property (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_expression,
+ gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRDeclaration *decl = NULL,
+ *decl2 = NULL;
+ CRString *str = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ /*
+ *make sure a current ruleset statement has been allocated
+ *already.
+ */
+ g_return_if_fail
+ (ctxt->cur_stmt
+ &&
+ (ctxt->cur_stmt->type == RULESET_STMT
+ || ctxt->cur_stmt->type == AT_FONT_FACE_RULE_STMT
+ || ctxt->cur_stmt->type == AT_PAGE_RULE_STMT));
+
+ if (a_name) {
+ str = cr_string_dup (a_name);
+ g_return_if_fail (str);
+ }
+
+ /*instantiates a new declaration */
+ decl = cr_declaration_new (ctxt->cur_stmt, str, a_expression);
+ g_return_if_fail (decl);
+ str = NULL;
+ decl->important = a_important;
+ /*
+ *add the new declaration to the current statement
+ *being build.
+ */
+ switch (ctxt->cur_stmt->type) {
+ case RULESET_STMT:
+ decl2 = cr_declaration_append
+ (ctxt->cur_stmt->kind.ruleset->decl_list, decl);
+ if (!decl2) {
+ cr_declaration_destroy (decl);
+ cr_utils_trace_info
+ ("Could not append decl to ruleset");
+ goto error;
+ }
+ ctxt->cur_stmt->kind.ruleset->decl_list = decl2;
+ decl = NULL;
+ decl2 = NULL;
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ decl2 = cr_declaration_append
+ (ctxt->cur_stmt->kind.font_face_rule->decl_list,
+ decl);
+ if (!decl2) {
+ cr_declaration_destroy (decl);
+ cr_utils_trace_info
+ ("Could not append decl to ruleset");
+ goto error;
+ }
+ ctxt->cur_stmt->kind.font_face_rule->decl_list = decl2;
+ decl = NULL;
+ decl2 = NULL;
+ break;
+ case AT_PAGE_RULE_STMT:
+ decl2 = cr_declaration_append
+ (ctxt->cur_stmt->kind.page_rule->decl_list, decl);
+ if (!decl2) {
+ cr_declaration_destroy (decl);
+ cr_utils_trace_info
+ ("Could not append decl to ruleset");
+ goto error;
+ }
+ ctxt->cur_stmt->kind.page_rule->decl_list = decl2;
+ decl = NULL;
+ decl2 = NULL;
+ break;
+
+ default:
+ goto error;
+ break;
+ }
+
+ return;
+
+ error:
+ if (str) {
+ g_free (str);
+ str = NULL;
+ }
+
+ if (decl) {
+ cr_declaration_destroy (decl);
+ decl = NULL;
+ }
+}
+
+static void
+error (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+}
+
+static void
+unrecoverable_error (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK);
+
+ if (ctxt) {
+ if (ctxt->stylesheet) {
+ status = cr_doc_handler_set_result
+ (a_this, ctxt->stylesheet);
+ g_return_if_fail (status == CR_OK);
+ }
+ g_free (ctxt);
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ }
+}
+
+/********************************************
+ *Public methods
+ ********************************************/
+
+/**
+ * cr_om_parser_new:
+ *@a_input: the input stream.
+ *
+ *Constructor of the CROMParser.
+ *Returns the newly built instance of #CROMParser.
+ */
+CROMParser *
+cr_om_parser_new (CRInput * a_input)
+{
+ CROMParser *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ result = g_try_malloc (sizeof (CROMParser));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CROMParser));
+ PRIVATE (result) = g_try_malloc (sizeof (CROMParserPriv));
+
+ if (!PRIVATE (result)) {
+ cr_utils_trace_info ("Out of memory");
+ goto error;
+ }
+
+ memset (PRIVATE (result), 0, sizeof (CROMParserPriv));
+
+ PRIVATE (result)->parser = cr_parser_new_from_input (a_input);
+
+ if (!PRIVATE (result)->parser) {
+ cr_utils_trace_info ("parsing instantiation failed");
+ goto error;
+ }
+
+ status = cr_om_parser_init_default_sac_handler (result);
+
+ if (status != CR_OK) {
+ goto error;
+ }
+
+ return result;
+
+ error:
+
+ if (result) {
+ cr_om_parser_destroy (result);
+ }
+
+ return NULL;
+}
+
+/**
+ * cr_om_parser_parse_buf:
+ *@a_this: the current instance of #CROMParser.
+ *@a_buf: the in memory buffer to parse.
+ *@a_len: the length of the in memory buffer in number of bytes.
+ *@a_enc: the encoding of the in memory buffer.
+ *@a_result: out parameter the resulting style sheet
+ *
+ *Parses the content of an in memory buffer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_om_parser_parse_buf (CROMParser * a_this,
+ const guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc, CRStyleSheet ** a_result)
+{
+
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && a_result, CR_BAD_PARAM_ERROR);
+
+ if (!PRIVATE (a_this)->parser) {
+ PRIVATE (a_this)->parser = cr_parser_new (NULL);
+ }
+
+ status = cr_parser_parse_buf (PRIVATE (a_this)->parser,
+ a_buf, a_len, a_enc);
+
+ if (status == CR_OK) {
+ CRStyleSheet *result = NULL;
+ CRStyleSheet **resultptr = NULL;
+ CRDocHandler *sac_handler = NULL;
+
+ cr_parser_get_sac_handler (PRIVATE (a_this)->parser,
+ &sac_handler);
+ g_return_val_if_fail (sac_handler, CR_ERROR);
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ g_return_val_if_fail (status == CR_OK, status);
+
+ if (result)
+ *a_result = result;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_simply_parse_buf:
+ *@a_buf: the css2 in memory buffer.
+ *@a_len: the length of the in memory buffer.
+ *@a_enc: the encoding of the in memory buffer.
+ *@a_result: out parameter. The resulting css2 style sheet.
+ *
+ *The simpler way to parse an in memory css2 buffer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_om_parser_simply_parse_buf (const guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ CRStyleSheet ** a_result)
+{
+ CROMParser *parser = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_om_parser_new (NULL);
+ if (!parser) {
+ cr_utils_trace_info ("Could not create om parser");
+ cr_utils_trace_info ("System possibly out of memory");
+ return CR_ERROR;
+ }
+
+ status = cr_om_parser_parse_buf (parser, a_buf, a_len,
+ a_enc, a_result);
+
+ if (parser) {
+ cr_om_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_parse_file:
+ *@a_this: the current instance of the cssom parser.
+ *@a_file_uri: the uri of the file.
+ *(only local file paths are supported so far)
+ *@a_enc: the encoding of the file.
+ *@a_result: out parameter. A pointer
+ *the build css object model.
+ *
+ *Parses a css2 stylesheet contained
+ *in a file.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_om_parser_parse_file (CROMParser * a_this,
+ const guchar * a_file_uri,
+ enum CREncoding a_enc, CRStyleSheet ** a_result)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && a_file_uri && a_result,
+ CR_BAD_PARAM_ERROR);
+
+ if (!PRIVATE (a_this)->parser) {
+ PRIVATE (a_this)->parser = cr_parser_new_from_file
+ (a_file_uri, a_enc);
+ }
+
+ status = cr_parser_parse_file (PRIVATE (a_this)->parser,
+ a_file_uri, a_enc);
+
+ if (status == CR_OK) {
+ CRStyleSheet *result = NULL;
+ CRStyleSheet **resultptr = NULL;
+ CRDocHandler *sac_handler = NULL;
+
+ cr_parser_get_sac_handler (PRIVATE (a_this)->parser,
+ &sac_handler);
+ g_return_val_if_fail (sac_handler, CR_ERROR);
+ resultptr = &result;
+ status = cr_doc_handler_get_result
+ (sac_handler, (gpointer *) resultptr);
+ g_return_val_if_fail (status == CR_OK, status);
+ if (result)
+ *a_result = result;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_simply_parse_file:
+ *@a_file_path: the css2 local file path.
+ *@a_enc: the file encoding.
+ *@a_result: out parameter. The returned css stylesheet.
+ *Must be freed by the caller using cr_stylesheet_destroy.
+ *
+ *The simpler method to parse a css2 file.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ *Note that this method uses cr_om_parser_parse_file() so both methods
+ *have the same return values.
+ */
+enum CRStatus
+cr_om_parser_simply_parse_file (const guchar * a_file_path,
+ enum CREncoding a_enc,
+ CRStyleSheet ** a_result)
+{
+ CROMParser *parser = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_om_parser_new (NULL);
+ if (!parser) {
+ cr_utils_trace_info ("Could not allocate om parser");
+ cr_utils_trace_info ("System may be out of memory");
+ return CR_ERROR;
+ }
+
+ status = cr_om_parser_parse_file (parser, a_file_path,
+ a_enc, a_result);
+ if (parser) {
+ cr_om_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_parse_paths_to_cascade:
+ *@a_this: the current instance of #CROMParser
+ *@a_author_path: the path to the author stylesheet
+ *@a_user_path: the path to the user stylesheet
+ *@a_ua_path: the path to the User Agent stylesheet
+ *@a_encoding: the encoding of the sheets.
+ *@a_result: out parameter. The resulting cascade if the parsing
+ *was okay
+ *
+ *Parses three sheets located by their paths and build a cascade
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise
+ */
+enum CRStatus
+cr_om_parser_parse_paths_to_cascade (CROMParser * a_this,
+ const guchar * a_author_path,
+ const guchar * a_user_path,
+ const guchar * a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result)
+{
+ enum CRStatus status = CR_OK;
+
+ /*0->author sheet, 1->user sheet, 2->UA sheet */
+ CRStyleSheet *sheets[3];
+ guchar *paths[3];
+ CRCascade *result = NULL;
+ gint i = 0;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ memset (sheets, 0, sizeof (CRStyleSheet*) * 3);
+ paths[0] = (guchar *) a_author_path;
+ paths[1] = (guchar *) a_user_path;
+ paths[2] = (guchar *) a_ua_path;
+
+ for (i = 0; i < 3; i++) {
+ status = cr_om_parser_parse_file (a_this, paths[i],
+ a_encoding, &sheets[i]);
+ if (status != CR_OK) {
+ if (sheets[i]) {
+ cr_stylesheet_unref (sheets[i]);
+ sheets[i] = NULL;
+ }
+ continue;
+ }
+ }
+ result = cr_cascade_new (sheets[0], sheets[1], sheets[2]);
+ if (!result) {
+ for (i = 0; i < 3; i++) {
+ cr_stylesheet_unref (sheets[i]);
+ sheets[i] = 0;
+ }
+ return CR_ERROR;
+ }
+ *a_result = result;
+ return CR_OK;
+}
+
+/**
+ * cr_om_parser_simply_parse_paths_to_cascade:
+ *@a_author_path: the path to the author stylesheet
+ *@a_user_path: the path to the user stylesheet
+ *@a_ua_path: the path to the User Agent stylesheet
+ *@a_encoding: the encoding of the sheets.
+ *@a_result: out parameter. The resulting cascade if the parsing
+ *was okay
+ *
+ *Parses three sheets located by their paths and build a cascade
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise
+ */
+enum CRStatus
+cr_om_parser_simply_parse_paths_to_cascade (const guchar * a_author_path,
+ const guchar * a_user_path,
+ const guchar * a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result)
+{
+ enum CRStatus status = CR_OK;
+ CROMParser *parser = NULL;
+
+ parser = cr_om_parser_new (NULL);
+ if (!parser) {
+ cr_utils_trace_info ("could not allocated om parser");
+ cr_utils_trace_info ("System may be out of memory");
+ return CR_ERROR;
+ }
+ status = cr_om_parser_parse_paths_to_cascade (parser,
+ a_author_path,
+ a_user_path,
+ a_ua_path,
+ a_encoding, a_result);
+ if (parser) {
+ cr_om_parser_destroy (parser);
+ parser = NULL;
+ }
+ return status;
+}
+
+/**
+ * cr_om_parser_destroy:
+ *@a_this: the current instance of #CROMParser.
+ *
+ *Destructor of the #CROMParser.
+ */
+void
+cr_om_parser_destroy (CROMParser * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ if (PRIVATE (a_this)->parser) {
+ cr_parser_destroy (PRIVATE (a_this)->parser);
+ PRIVATE (a_this)->parser = NULL;
+ }
+
+ if (PRIVATE (a_this)) {
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ a_this = NULL;
+ }
+}
diff --git a/src/st/croco/cr-om-parser.h b/src/st/croco/cr-om-parser.h
new file mode 100644
index 0000000..13d35b1
--- /dev/null
+++ b/src/st/croco/cr-om-parser.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the
+ * GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+#ifndef __CR_OM_PARSER_H__
+#define __CR_OM_PARSER_H__
+
+#include "cr-parser.h"
+#include "cr-cascade.h"
+
+
+/**
+ *@file
+ *The definition of the CSS Object Model Parser.
+ *This parser uses (and sits) the SAC api of libcroco defined
+ *in cr-parser.h and cr-doc-handler.h
+ */
+
+G_BEGIN_DECLS
+
+typedef struct _CROMParser CROMParser ;
+typedef struct _CROMParserPriv CROMParserPriv ;
+
+/**
+ *The Object model parser.
+ *Can parse a css file and build a css object model.
+ *This parser uses an instance of #CRParser and defines
+ *a set of SAC callbacks to build the Object Model.
+ */
+struct _CROMParser
+{
+ CROMParserPriv *priv ;
+} ;
+
+CROMParser * cr_om_parser_new (CRInput *a_input) ;
+
+
+enum CRStatus cr_om_parser_simply_parse_file (const guchar *a_file_path,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_parse_file (CROMParser *a_this,
+ const guchar *a_file_uri,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_simply_parse_buf (const guchar *a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_parse_buf (CROMParser *a_this,
+ const guchar *a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_parse_paths_to_cascade (CROMParser *a_this,
+ const guchar *a_author_path,
+ const guchar *a_user_path,
+ const guchar *a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result) ;
+
+enum CRStatus cr_om_parser_simply_parse_paths_to_cascade (const guchar *a_author_path,
+ const guchar *a_user_path,
+ const guchar *a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result) ;
+
+void cr_om_parser_destroy (CROMParser *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_OM_PARSER_H__*/
diff --git a/src/st/croco/cr-parser.c b/src/st/croco/cr-parser.c
new file mode 100644
index 0000000..d4f40cf
--- /dev/null
+++ b/src/st/croco/cr-parser.c
@@ -0,0 +1,4539 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the
+ * GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+/**
+ *@CRParser:
+ *
+ *The definition of the #CRParser class.
+ */
+
+#include "string.h"
+#include "cr-parser.h"
+#include "cr-num.h"
+#include "cr-term.h"
+#include "cr-simple-sel.h"
+#include "cr-attr-sel.h"
+
+/*
+ *Random notes:
+ *CSS core syntax vs CSS level 2 syntax
+ *=====================================
+ *
+ *One must keep in mind
+ *that css UA must comply with two syntaxes.
+ *
+ *1/the specific syntax that defines the css language
+ *for a given level of specification (e.g css2 syntax
+ *defined in appendix D.1 of the css2 spec)
+ *
+ *2/the core (general) syntax that is there to allow
+ *UAs to parse style sheets written in levels of CSS that
+ *didn't exist at the time the UAs were created.
+ *
+ *the name of parsing functions (or methods) contained in this file
+ *follows the following scheme: cr_parser_parse_<production_name> (...) ;
+ *where <production_name> is the name
+ *of a production of the css2 language.
+ *When a given production is
+ *defined by the css2 level grammar *and* by the
+ *css core syntax, there will be two functions to parse that production:
+ *one will parse the production defined by the css2 level grammar and the
+ *other will parse the production defined by the css core grammar.
+ *The css2 level grammar related parsing function will be called:
+ *cr_parser_parse_<production_name> (...) ;
+ *Then css core grammar related parsing function will be called:
+ *cr_parser_parse_<production_name>_core (...) ;
+ *
+ *If a production is defined only by the css core grammar, then
+ *it will be named:
+ *cr_parser_parse_<production_name>_core (...) ;
+ */
+
+typedef struct _CRParserError CRParserError;
+
+/**
+ *An abstraction of an error reported by by the
+ *parsing routines.
+ */
+struct _CRParserError {
+ guchar *msg;
+ enum CRStatus status;
+ glong line;
+ glong column;
+ glong byte_num;
+};
+
+enum CRParserState {
+ READY_STATE = 0,
+ TRY_PARSE_CHARSET_STATE,
+ CHARSET_PARSED_STATE,
+ TRY_PARSE_IMPORT_STATE,
+ IMPORT_PARSED_STATE,
+ TRY_PARSE_RULESET_STATE,
+ RULESET_PARSED_STATE,
+ TRY_PARSE_MEDIA_STATE,
+ MEDIA_PARSED_STATE,
+ TRY_PARSE_PAGE_STATE,
+ PAGE_PARSED_STATE,
+ TRY_PARSE_FONT_FACE_STATE,
+ FONT_FACE_PARSED_STATE
+} ;
+
+/**
+ *The private attributes of
+ *#CRParser.
+ */
+struct _CRParserPriv {
+ /**
+ *The tokenizer
+ */
+ CRTknzr *tknzr;
+
+ /**
+ *The sac handlers to call
+ *to notify the parsing of
+ *the css2 constructions.
+ */
+ CRDocHandler *sac_handler;
+
+ /**
+ *A stack of errors reported
+ *by the parsing routines.
+ *Contains instance of #CRParserError.
+ *This pointer is the top of the stack.
+ */
+ GList *err_stack;
+
+ enum CRParserState state;
+ gboolean resolve_import;
+ gboolean is_case_sensitive;
+ gboolean use_core_grammar;
+};
+
+#define PRIVATE(obj) ((obj)->priv)
+
+#define CHARS_TAB_SIZE 12
+
+#define RECURSIVE_CALLERS_LIMIT 100
+
+/**
+ * IS_NUM:
+ *@a_char: the char to test.
+ *return TRUE if the character is a number ([0-9]), FALSE otherwise
+ */
+#define IS_NUM(a_char) (((a_char) >= '0' && (a_char) <= '9')?TRUE:FALSE)
+
+/**
+ *Checks if 'status' equals CR_OK. If not, goto the 'error' label.
+ *
+ *@param status the status (of type enum CRStatus) to test.
+ *@param is_exception if set to FALSE, the final status returned
+ *by the current function will be CR_PARSING_ERROR. If set to TRUE, the
+ *current status will be the current value of the 'status' variable.
+ *
+ */
+#define CHECK_PARSING_STATUS(status, is_exception) \
+if ((status) != CR_OK) \
+{ \
+ if (is_exception == FALSE) \
+ { \
+ status = CR_PARSING_ERROR ; \
+ } \
+ goto error ; \
+}
+
+/**
+ * CHECK_PARSING_STATUS_ERR:
+ *@a_this: the current instance of #CRParser .
+ *@a_status: the status to check. Is of type enum #CRStatus.
+ *@a_is_exception: in case of error, if is TRUE, the status
+ *is set to CR_PARSING_ERROR before goto error. If is false, the
+ *real low level status is kept and will be returned by the
+ *upper level function that called this macro. Usually,this must
+ *be set to FALSE.
+ *
+ *same as CHECK_PARSING_STATUS() but this one pushes an error
+ *on the parser error stack when an error arises.
+ *
+ */
+#define CHECK_PARSING_STATUS_ERR(a_this, a_status, a_is_exception,\
+ a_err_msg, a_err_status) \
+if ((a_status) != CR_OK) \
+{ \
+ if (a_is_exception == FALSE) a_status = CR_PARSING_ERROR ; \
+ cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \
+ goto error ; \
+}
+
+/**
+ *Peeks the next char from the input stream of the current parser
+ *by invoking cr_tknzr_input_peek_char().
+ *invokes CHECK_PARSING_STATUS on the status returned by
+ *cr_tknzr_peek_char().
+ *
+ *@param a_this the current instance of #CRParser.
+ *@param a_to_char a pointer to the char where to store the
+ *char peeked.
+ */
+#define PEEK_NEXT_CHAR(a_this, a_to_char) \
+{\
+status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, a_to_char) ; \
+CHECK_PARSING_STATUS (status, TRUE) \
+}
+
+/**
+ *Reads the next char from the input stream of the current parser.
+ *In case of error, jumps to the "error:" label located in the
+ *function where this macro is called.
+ *@param a_this the current instance of #CRParser
+ *@param to_char a pointer to the guint32 char where to store
+ *the character read.
+ */
+#define READ_NEXT_CHAR(a_this, a_to_char) \
+status = cr_tknzr_read_char (PRIVATE (a_this)->tknzr, a_to_char) ; \
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Gets information about the current position in
+ *the input of the parser.
+ *In case of failure, this macro returns from the
+ *calling function and
+ *returns a status code of type enum #CRStatus.
+ *@param a_this the current instance of #CRParser.
+ *@param a_pos out parameter. A pointer to the position
+ *inside the current parser input. Must
+ */
+#define RECORD_INITIAL_POS(a_this, a_pos) \
+status = cr_tknzr_get_cur_pos (PRIVATE \
+(a_this)->tknzr, a_pos) ; \
+g_return_val_if_fail (status == CR_OK, status)
+
+/**
+ *Gets the address of the current byte inside the
+ *parser input.
+ *@param parser the current instance of #CRParser.
+ *@param addr out parameter a pointer (guchar*)
+ *to where the address must be put.
+ */
+#define RECORD_CUR_BYTE_ADDR(a_this, a_addr) \
+status = cr_tknzr_get_cur_byte_addr \
+ (PRIVATE (a_this)->tknzr, a_addr) ; \
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Peeks a byte from the topmost parser input at
+ *a given offset from the current position.
+ *If it fails, goto the "error:" label.
+ *
+ *@param a_parser the current instance of #CRParser.
+ *@param a_offset the offset of the byte to peek, the
+ *current byte having the offset '0'.
+ *@param a_byte_ptr out parameter a pointer (guchar*) to
+ *where the peeked char is to be stored.
+ */
+#define PEEK_BYTE(a_parser, a_offset, a_byte_ptr) \
+status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr, \
+ a_offset, \
+ a_byte_ptr) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+#define BYTE(a_parser, a_offset, a_eof) \
+cr_tknzr_peek_byte2 (PRIVATE (a_this)->tknzr, a_offset, a_eof)
+
+/**
+ *Reads a byte from the topmost parser input
+ *steam.
+ *If it fails, goto the "error" label.
+ *@param a_this the current instance of #CRParser.
+ *@param a_byte_ptr the guchar * where to put the read char.
+ */
+#define READ_NEXT_BYTE(a_this, a_byte_ptr) \
+status = cr_tknzr_read_byte (PRIVATE (a_this)->tknzr, a_byte_ptr) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skips a given number of byte in the topmost
+ *parser input. Don't update line and column number.
+ *In case of error, jumps to the "error:" label
+ *of the surrounding function.
+ *@param a_parser the current instance of #CRParser.
+ *@param a_nb_bytes the number of bytes to skip.
+ */
+#define SKIP_BYTES(a_this, a_nb_bytes) \
+status = cr_tknzr_seek_index (PRIVATE (a_this)->tknzr, \
+ CR_SEEK_CUR, a_nb_bytes) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skip utf8 encoded characters.
+ *Updates line and column numbers.
+ *@param a_parser the current instance of #CRParser.
+ *@param a_nb_chars the number of chars to skip. Must be of
+ *type glong.
+ */
+#define SKIP_CHARS(a_parser, a_nb_chars) \
+{ \
+glong nb_chars = a_nb_chars ; \
+status = cr_tknzr_consume_chars \
+ (PRIVATE (a_parser)->tknzr,0, &nb_chars) ; \
+CHECK_PARSING_STATUS (status, TRUE) ; \
+}
+
+/**
+ *Tests the condition and if it is false, sets
+ *status to "CR_PARSING_ERROR" and goto the 'error'
+ *label.
+ *@param condition the condition to test.
+ */
+#define ENSURE_PARSING_COND(condition) \
+if (! (condition)) {status = CR_PARSING_ERROR; goto error ;}
+
+#define ENSURE_PARSING_COND_ERR(a_this, a_condition, \
+ a_err_msg, a_err_status) \
+if (! (a_condition)) \
+{ \
+ status = CR_PARSING_ERROR; \
+ cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \
+ goto error ; \
+}
+
+#define GET_NEXT_TOKEN(a_this, a_token_ptr) \
+status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, \
+ a_token_ptr) ; \
+ENSURE_PARSING_COND (status == CR_OK) ;
+
+#ifdef WITH_UNICODE_ESCAPE_AND_RANGE
+static enum CRStatus cr_parser_parse_unicode_escape (CRParser * a_this,
+ guint32 * a_unicode);
+static enum CRStatus cr_parser_parse_escape (CRParser * a_this,
+ guint32 * a_esc_code);
+
+static enum CRStatus cr_parser_parse_unicode_range (CRParser * a_this,
+ CRString ** a_inf,
+ CRString ** a_sup);
+#endif
+
+static enum CRStatus cr_parser_parse_stylesheet_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_atrule_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_ruleset_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_selector_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_declaration_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_any_core (CRParser * a_this,
+ guint n_calls);
+
+static enum CRStatus cr_parser_parse_block_core (CRParser * a_this,
+ guint n_calls);
+
+static enum CRStatus cr_parser_parse_value_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_string (CRParser * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_parser_parse_ident (CRParser * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_parser_parse_uri (CRParser * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_parser_parse_function (CRParser * a_this,
+ CRString ** a_func_name,
+ CRTerm ** a_expr);
+static enum CRStatus cr_parser_parse_property (CRParser * a_this,
+ CRString ** a_property);
+
+static enum CRStatus cr_parser_parse_attribute_selector (CRParser * a_this,
+ CRAttrSel ** a_sel);
+
+static enum CRStatus cr_parser_parse_simple_selector (CRParser * a_this,
+ CRSimpleSel ** a_sel);
+
+static enum CRStatus cr_parser_parse_simple_sels (CRParser * a_this,
+ CRSimpleSel ** a_sel);
+
+static CRParserError *cr_parser_error_new (const guchar * a_msg,
+ enum CRStatus);
+
+static void cr_parser_error_set_msg (CRParserError * a_this,
+ const guchar * a_msg);
+
+static void cr_parser_error_dump (CRParserError * a_this);
+
+static void cr_parser_error_set_status (CRParserError * a_this,
+ enum CRStatus a_status);
+
+static void cr_parser_error_set_pos (CRParserError * a_this,
+ glong a_line,
+ glong a_column, glong a_byte_num);
+static void
+ cr_parser_error_destroy (CRParserError * a_this);
+
+static enum CRStatus cr_parser_push_error (CRParser * a_this,
+ const guchar * a_msg,
+ enum CRStatus a_status);
+
+static enum CRStatus cr_parser_dump_err_stack (CRParser * a_this,
+ gboolean a_clear_errs);
+static enum CRStatus
+ cr_parser_clear_errors (CRParser * a_this);
+
+/*****************************
+ *error managemet methods
+ *****************************/
+
+/**
+ *Constructor of #CRParserError class.
+ *@param a_msg the brute error message.
+ *@param a_status the error status.
+ *@return the newly built instance of #CRParserError.
+ */
+static CRParserError *
+cr_parser_error_new (const guchar * a_msg, enum CRStatus a_status)
+{
+ CRParserError *result = NULL;
+
+ result = g_try_malloc (sizeof (CRParserError));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRParserError));
+
+ cr_parser_error_set_msg (result, a_msg);
+ cr_parser_error_set_status (result, a_status);
+
+ return result;
+}
+
+/**
+ *Sets the message associated to this instance of #CRError.
+ *@param a_this the current instance of #CRParserError.
+ *@param a_msg the new message.
+ */
+static void
+cr_parser_error_set_msg (CRParserError * a_this, const guchar * a_msg)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->msg) {
+ g_free (a_this->msg);
+ }
+
+ a_this->msg = (guchar *) g_strdup ((const gchar *) a_msg);
+}
+
+/**
+ *Sets the error status.
+ *@param a_this the current instance of #CRParserError.
+ *@param a_status the new error status.
+ *
+ */
+static void
+cr_parser_error_set_status (CRParserError * a_this, enum CRStatus a_status)
+{
+ g_return_if_fail (a_this);
+
+ a_this->status = a_status;
+}
+
+/**
+ *Sets the position of the parser error.
+ *@param a_this the current instance of #CRParserError.
+ *@param a_line the line number.
+ *@param a_column the column number.
+ *@param a_byte_num the byte number.
+ */
+static void
+cr_parser_error_set_pos (CRParserError * a_this,
+ glong a_line, glong a_column, glong a_byte_num)
+{
+ g_return_if_fail (a_this);
+
+ a_this->line = a_line;
+ a_this->column = a_column;
+ a_this->byte_num = a_byte_num;
+}
+
+static void
+cr_parser_error_dump (CRParserError * a_this)
+{
+ g_return_if_fail (a_this);
+
+ g_printerr ("parsing error: %ld:%ld:", a_this->line, a_this->column);
+
+ g_printerr ("%s\n", a_this->msg);
+}
+
+/**
+ *The destructor of #CRParserError.
+ *@param a_this the current instance of #CRParserError.
+ */
+static void
+cr_parser_error_destroy (CRParserError * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->msg) {
+ g_free (a_this->msg);
+ a_this->msg = NULL;
+ }
+
+ g_free (a_this);
+}
+
+/**
+ *Pushes an error on the parser error stack.
+ *@param a_this the current instance of #CRParser.
+ *@param a_msg the error message.
+ *@param a_status the error status.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_push_error (CRParser * a_this,
+ const guchar * a_msg, enum CRStatus a_status)
+{
+ enum CRStatus status = CR_OK;
+
+ CRParserError *error = NULL;
+ CRInputPos pos;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_msg, CR_BAD_PARAM_ERROR);
+
+ error = cr_parser_error_new (a_msg, a_status);
+
+ g_return_val_if_fail (error, CR_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &pos);
+
+ cr_parser_error_set_pos
+ (error, pos.line, pos.col, pos.next_byte_index - 1);
+
+ PRIVATE (a_this)->err_stack =
+ g_list_prepend (PRIVATE (a_this)->err_stack, error);
+
+ if (PRIVATE (a_this)->err_stack == NULL)
+ goto error;
+
+ return CR_OK;
+
+ error:
+
+ if (error) {
+ cr_parser_error_destroy (error);
+ error = NULL;
+ }
+
+ return status;
+}
+
+/**
+ *Dumps the error stack on stdout.
+ *@param a_this the current instance of #CRParser.
+ *@param a_clear_errs whether to clear the error stack
+ *after the dump or not.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_dump_err_stack (CRParser * a_this, gboolean a_clear_errs)
+{
+ GList *cur = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->err_stack == NULL)
+ return CR_OK;
+
+ for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) {
+ cr_parser_error_dump ((CRParserError *) cur->data);
+ }
+
+ if (a_clear_errs == TRUE) {
+ cr_parser_clear_errors (a_this);
+ }
+
+ return CR_OK;
+}
+
+/**
+ *Clears all the errors contained in the parser error stack.
+ *Frees all the errors, and the stack that contains'em.
+ *@param a_this the current instance of #CRParser.
+ */
+static enum CRStatus
+cr_parser_clear_errors (CRParser * a_this)
+{
+ GList *cur = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) {
+ if (cur->data) {
+ cr_parser_error_destroy ((CRParserError *)
+ cur->data);
+ }
+ }
+
+ if (PRIVATE (a_this)->err_stack) {
+ g_list_free (PRIVATE (a_this)->err_stack);
+ PRIVATE (a_this)->err_stack = NULL;
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_try_to_skip_spaces_and_comments:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Same as cr_parser_try_to_skip_spaces() but this one skips
+ *spaces and comments.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_try_to_skip_spaces_and_comments (CRParser * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+ CRToken *token = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK)
+ goto error;
+ }
+ while ((token != NULL)
+ && (token->type == COMMENT_TK || token->type == S_TK));
+
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+
+ return status;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ return status;
+}
+
+/***************************************
+ *End of Parser input handling routines
+ ***************************************/
+
+
+/*************************************
+ *Non trivial terminal productions
+ *parsing routines
+ *************************************/
+
+/**
+ *Parses a css stylesheet following the core css grammar.
+ *This is mainly done for test purposes.
+ *During the parsing, no callback is called. This is just
+ *to validate that the stylesheet is well formed according to the
+ *css core syntax.
+ *stylesheet : [ CDO | CDC | S | statement ]*;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_stylesheet_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ continue_parsing:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ goto done;
+ } else if (status != CR_OK) {
+ goto error;
+ }
+
+ switch (token->type) {
+
+ case CDO_TK:
+ case CDC_TK:
+ goto continue_parsing;
+ break;
+ default:
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_statement_core (a_this);
+ cr_parser_clear_errors (a_this);
+ if (status == CR_OK) {
+ goto continue_parsing;
+ } else if (status == CR_END_OF_INPUT_ERROR) {
+ goto done;
+ } else {
+ goto error;
+ }
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+ cr_parser_push_error
+ (a_this, (const guchar *) "could not recognize next production", CR_ERROR);
+
+ cr_parser_dump_err_stack (a_this, TRUE);
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses an at-rule as defined by the css core grammar
+ *in chapter 4.1 in the css2 spec.
+ *at-rule : ATKEYWORD S* any* [ block | ';' S* ];
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_atrule_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ &&
+ (token->type == ATKEYWORD_TK
+ || token->type == IMPORT_SYM_TK
+ || token->type == PAGE_SYM_TK
+ || token->type == MEDIA_SYM_TK
+ || token->type == FONT_FACE_SYM_TK
+ || token->type == CHARSET_SYM_TK));
+
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ do {
+ status = cr_parser_parse_any_core (a_this, 0);
+ } while (status == CR_OK);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == CBO_TK) {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_block_core (a_this, 0);
+ CHECK_PARSING_STATUS (status,
+ FALSE);
+ goto done;
+ } else if (token->type == SEMICOLON_TK) {
+ goto done;
+ } else {
+ status = CR_PARSING_ERROR ;
+ goto error;
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr,
+ &init_pos);
+ return status;
+}
+
+/**
+ *Parses a ruleset as defined by the css core grammar in chapter
+ *4.1 of the css2 spec.
+ *ruleset ::= selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_ruleset_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_selector_core (a_this);
+
+ ENSURE_PARSING_COND (status == CR_OK
+ || status == CR_PARSING_ERROR
+ || status == CR_END_OF_INPUT_ERROR);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration_core (a_this);
+
+ parse_declaration_list:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+ if (token->type == CBC_TK) {
+ goto done;
+ }
+
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == SEMICOLON_TK);
+
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration_core (a_this);
+ cr_parser_clear_errors (a_this);
+ ENSURE_PARSING_COND (status == CR_OK || status == CR_PARSING_ERROR);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+ if (token->type == CBC_TK) {
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto done;
+ } else {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ goto parse_declaration_list;
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_OK) {
+ return CR_OK;
+ }
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "selector" as specified by the css core
+ *grammar.
+ *selector : any+;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_selector_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_any_core (a_this, 0);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ do {
+ status = cr_parser_parse_any_core (a_this, 0);
+
+ } while (status == CR_OK);
+
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "block" as defined in the css core grammar
+ *in chapter 4.1 of the css2 spec.
+ *block ::= '{' S* [ any | block | ATKEYWORD S* | ';' ]* '}' S*;
+ *@param a_this the current instance of #CRParser.
+ *@param n_calls used to limit recursion depth
+ *FIXME: code this function.
+ */
+static enum CRStatus
+cr_parser_parse_block_core (CRParser * a_this,
+ guint n_calls)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (n_calls > RECURSIVE_CALLERS_LIMIT)
+ return CR_ERROR;
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+
+ parse_block_content:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == CBC_TK) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto done;
+ } else if (token->type == SEMICOLON_TK) {
+ goto parse_block_content;
+ } else if (token->type == ATKEYWORD_TK) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto parse_block_content;
+ } else if (token->type == CBO_TK) {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_block_core (a_this, n_calls + 1);
+ CHECK_PARSING_STATUS (status, FALSE);
+ goto parse_block_content;
+ } else {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ CHECK_PARSING_STATUS (status, FALSE);
+ goto parse_block_content;
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_OK)
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+static enum CRStatus
+cr_parser_parse_declaration_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+ CRString *prop = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_property (a_this, &prop);
+ CHECK_PARSING_STATUS (status, FALSE);
+ cr_parser_clear_errors (a_this);
+ ENSURE_PARSING_COND (status == CR_OK && prop);
+ cr_string_destroy (prop);
+ prop = NULL;
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == DELIM_TK
+ && token->u.unichar == ':');
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_value_core (a_this);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ return CR_OK;
+
+ error:
+
+ if (prop) {
+ cr_string_destroy (prop);
+ prop = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "value" production as defined by the css core grammar
+ *in chapter 4.1.
+ *value ::= [ any | block | ATKEYWORD S* ]+;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_value_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+ glong ref = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ continue_parsing:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ switch (token->type) {
+ case CBO_TK:
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_block_core (a_this, 0);
+ CHECK_PARSING_STATUS (status, FALSE);
+ ref++;
+ goto continue_parsing;
+
+ case ATKEYWORD_TK:
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ ref++;
+ goto continue_parsing;
+
+ default:
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_any_core (a_this, 0);
+ if (status == CR_OK) {
+ ref++;
+ goto continue_parsing;
+ } else if (status == CR_PARSING_ERROR) {
+ status = CR_OK;
+ goto done;
+ } else {
+ goto error;
+ }
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_OK && ref)
+ return CR_OK;
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses an "any" as defined by the css core grammar in the
+ *css2 spec in chapter 4.1.
+ *any ::= [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
+ * | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
+ * | FUNCTION | DASHMATCH | '(' any* ')' | '[' any* ']' ] S*;
+ *
+ *@param a_this the current instance of #CRParser.
+ *@param n_calls used to limit recursion depth
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_any_core (CRParser * a_this,
+ guint n_calls)
+{
+ CRToken *token1 = NULL,
+ *token2 = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ if (n_calls > RECURSIVE_CALLERS_LIMIT)
+ return CR_ERROR;
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token1);
+
+ ENSURE_PARSING_COND (status == CR_OK && token1);
+
+ switch (token1->type) {
+ case IDENT_TK:
+ case NUMBER_TK:
+ case RGB_TK:
+ case PERCENTAGE_TK:
+ case DIMEN_TK:
+ case EMS_TK:
+ case EXS_TK:
+ case LENGTH_TK:
+ case ANGLE_TK:
+ case FREQ_TK:
+ case TIME_TK:
+ case STRING_TK:
+ case DELIM_TK:
+ case URI_TK:
+ case HASH_TK:
+ case UNICODERANGE_TK:
+ case INCLUDES_TK:
+ case DASHMATCH_TK:
+ case S_TK:
+ case COMMENT_TK:
+ case IMPORTANT_SYM_TK:
+ status = CR_OK;
+ break;
+ case FUNCTION_TK:
+ /*
+ *this case isn't specified by the spec but it
+ *does happen. So we have to handle it.
+ *We must consider function with parameters.
+ *We consider parameter as being an "any*" production.
+ */
+ do {
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ } while (status == CR_OK);
+
+ ENSURE_PARSING_COND (status == CR_PARSING_ERROR);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token2 && token2->type == PC_TK);
+ break;
+ case PO_TK:
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK && token2);
+
+ if (token2->type == PC_TK) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ goto done;
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token2);
+ token2 = NULL;
+ }
+
+ do {
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ } while (status == CR_OK);
+
+ ENSURE_PARSING_COND (status == CR_PARSING_ERROR);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token2 && token2->type == PC_TK);
+ status = CR_OK;
+ break;
+
+ case BO_TK:
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK && token2);
+
+ if (token2->type == BC_TK) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ goto done;
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token2);
+ token2 = NULL;
+ }
+
+ do {
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ } while (status == CR_OK);
+
+ ENSURE_PARSING_COND (status == CR_PARSING_ERROR);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token2 && token2->type == BC_TK);
+ status = CR_OK;
+ break;
+ default:
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ done:
+ if (token1) {
+ cr_token_destroy (token1);
+ token1 = NULL;
+ }
+
+ if (token2) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ }
+
+ return CR_OK;
+
+ error:
+
+ if (token1) {
+ cr_token_destroy (token1);
+ token1 = NULL;
+ }
+
+ if (token2) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+ return status;
+}
+
+/**
+ *Parses an attribute selector as defined in the css2 spec in
+ *appendix D.1:
+ *attrib ::= '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
+ * [ IDENT | STRING ] S* ]? ']'
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *#CRParser .
+ *@param a_sel out parameter. The successfully parsed attribute selector.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_attribute_selector (CRParser * a_this,
+ CRAttrSel ** a_sel)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRAttrSel *result = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == BO_TK);
+ cr_parsing_location_copy
+ (&location, &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ result = cr_attr_sel_new ();
+ if (!result) {
+ cr_utils_trace_info ("result failed") ;
+ status = CR_OUT_OF_MEMORY_ERROR ;
+ goto error ;
+ }
+ cr_parsing_location_copy (&result->location,
+ &location) ;
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == IDENT_TK);
+
+ result->name = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == INCLUDES_TK) {
+ result->match_way = INCLUDES;
+ goto parse_right_part;
+ } else if (token->type == DASHMATCH_TK) {
+ result->match_way = DASHMATCH;
+ goto parse_right_part;
+ } else if (token->type == DELIM_TK && token->u.unichar == '=') {
+ result->match_way = EQUALS;
+ goto parse_right_part;
+ } else if (token->type == BC_TK) {
+ result->match_way = SET;
+ goto done;
+ }
+
+ parse_right_part:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == IDENT_TK) {
+ result->value = token->u.str;
+ token->u.str = NULL;
+ } else if (token->type == STRING_TK) {
+ result->value = token->u.str;
+ token->u.str = NULL;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == BC_TK);
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (*a_sel) {
+ status = cr_attr_sel_append_attr_sel (*a_sel, result);
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else {
+ *a_sel = result;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (result) {
+ cr_attr_sel_destroy (result);
+ result = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "property" as specified by the css2 spec at [4.1.1]:
+ *property : IDENT S*;
+ *
+ *@param a_this the "this pointer" of the current instance of #CRParser.
+ *@param GString a_property out parameter. The parsed property without the
+ *trailing spaces. If *a_property is NULL, this function allocates a
+ *new instance of GString and set it content to the parsed property.
+ *If not, the property is just appended to a_property's previous content.
+ *In both cases, it is up to the caller to free a_property.
+ *@return CR_OK upon successful completion, CR_PARSING_ERROR if the
+ *next construction was not a "property", or an error code.
+ */
+static enum CRStatus
+cr_parser_parse_property (CRParser * a_this,
+ CRString ** a_property)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr
+ && a_property,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_ident (a_this, a_property);
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_term:
+ *@a_term: out parameter. The successfully parsed term.
+ *
+ *Parses a "term" as defined in the css2 spec, appendix D.1:
+ *term ::= unary_operator? [NUMBER S* | PERCENTAGE S* | LENGTH S* |
+ *EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* | function ] |
+ *STRING S* | IDENT S* | URI S* | RGB S* | UNICODERANGE S* | hexcolor
+ *
+ *TODO: handle parsing of 'RGB'
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_term (CRParser * a_this, CRTerm ** a_term)
+{
+ enum CRStatus status = CR_PARSING_ERROR;
+ CRInputPos init_pos;
+ CRTerm *result = NULL;
+ CRTerm *param = NULL;
+ CRToken *token = NULL;
+ CRString *func_name = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && a_term, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ result = cr_term_new ();
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK || !token)
+ goto error;
+
+ cr_parsing_location_copy (&location, &token->location) ;
+ if (token->type == DELIM_TK && token->u.unichar == '+') {
+ result->unary_op = PLUS_UOP;
+ cr_token_destroy (token) ;
+ token = NULL ;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK || !token)
+ goto error;
+ } else if (token->type == DELIM_TK && token->u.unichar == '-') {
+ result->unary_op = MINUS_UOP;
+ cr_token_destroy (token) ;
+ token = NULL ;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK || !token)
+ goto error;
+ }
+
+ if (token->type == EMS_TK
+ || token->type == EXS_TK
+ || token->type == LENGTH_TK
+ || token->type == ANGLE_TK
+ || token->type == TIME_TK
+ || token->type == FREQ_TK
+ || token->type == PERCENTAGE_TK
+ || token->type == NUMBER_TK) {
+ status = cr_term_set_number (result, token->u.num);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.num = NULL;
+ status = CR_OK;
+ } else if (token && token->type == FUNCTION_TK) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_function (a_this, &func_name,
+ &param);
+
+ if (status == CR_OK) {
+ status = cr_term_set_function (result,
+ func_name,
+ param);
+ CHECK_PARSING_STATUS (status, TRUE);
+ }
+ } else if (token && token->type == STRING_TK) {
+ status = cr_term_set_string (result,
+ token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else if (token && token->type == IDENT_TK) {
+ status = cr_term_set_ident (result, token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else if (token && token->type == URI_TK) {
+ status = cr_term_set_uri (result, token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else if (token && token->type == RGB_TK) {
+ status = cr_term_set_rgb (result, token->u.rgb);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.rgb = NULL;
+ } else if (token && token->type == UNICODERANGE_TK) {
+ result->type = TERM_UNICODERANGE;
+ status = CR_PARSING_ERROR;
+ } else if (token && token->type == HASH_TK) {
+ status = cr_term_set_hash (result, token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+
+ if (status != CR_OK) {
+ goto error;
+ }
+ cr_parsing_location_copy (&result->location,
+ &location) ;
+ *a_term = cr_term_append_term (*a_term, result);
+
+ result = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (result) {
+ cr_term_destroy (result);
+ result = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (param) {
+ cr_term_destroy (param);
+ param = NULL;
+ }
+
+ if (func_name) {
+ cr_string_destroy (func_name);
+ func_name = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_simple_selector:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_sel: out parameter. Is set to the successfully parsed simple
+ *selector.
+ *
+ *Parses a "simple_selector" as defined by the css2 spec in appendix D.1 :
+ *element_name? [ HASH | class | attrib | pseudo ]* S*
+ *and where pseudo is:
+ *pseudo ::= ':' [ IDENT | FUNCTION S* IDENT S* ')' ]
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_simple_selector (CRParser * a_this, CRSimpleSel ** a_sel)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRSimpleSel *sel = NULL;
+ CRAdditionalSel *add_sel_list = NULL;
+ gboolean found_sel = FALSE;
+ guint32 cur_char = 0;
+
+ g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ sel = cr_simple_sel_new ();
+ ENSURE_PARSING_COND (sel);
+
+ cr_parsing_location_copy
+ (&sel->location,
+ &token->location) ;
+
+ if (token && token->type == DELIM_TK
+ && token->u.unichar == '*') {
+ sel->type_mask |= UNIVERSAL_SELECTOR;
+ sel->name = cr_string_new_from_string ("*");
+ found_sel = TRUE;
+ } else if (token && token->type == IDENT_TK) {
+ sel->name = token->u.str;
+ sel->type_mask |= TYPE_SELECTOR;
+ token->u.str = NULL;
+ found_sel = TRUE;
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ for (;;) {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK)
+ goto error;
+
+ if (token && token->type == HASH_TK) {
+ /*we parsed an attribute id */
+ CRAdditionalSel *add_sel = NULL;
+
+ add_sel = cr_additional_sel_new_with_type
+ (ID_ADD_SELECTOR);
+
+ add_sel->content.id_name = token->u.str;
+ token->u.str = NULL;
+
+ cr_parsing_location_copy
+ (&add_sel->location,
+ &token->location) ;
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ found_sel = TRUE;
+ } else if (token && (token->type == DELIM_TK)
+ && (token->u.unichar == '.')) {
+ cr_token_destroy (token);
+ token = NULL;
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ if (token && token->type == IDENT_TK) {
+ CRAdditionalSel *add_sel = NULL;
+
+ add_sel = cr_additional_sel_new_with_type
+ (CLASS_ADD_SELECTOR);
+
+ add_sel->content.class_name = token->u.str;
+ token->u.str = NULL;
+
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ found_sel = TRUE;
+
+ cr_parsing_location_copy
+ (&add_sel->location,
+ & token->location) ;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ } else if (token && token->type == BO_TK) {
+ CRAttrSel *attr_sel = NULL;
+ CRAdditionalSel *add_sel = NULL;
+
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ if (status != CR_OK)
+ goto error;
+ token = NULL;
+
+ status = cr_parser_parse_attribute_selector
+ (a_this, &attr_sel);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ add_sel = cr_additional_sel_new_with_type
+ (ATTRIBUTE_ADD_SELECTOR);
+
+ ENSURE_PARSING_COND (add_sel != NULL);
+
+ add_sel->content.attr_sel = attr_sel;
+
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ found_sel = TRUE;
+ cr_parsing_location_copy
+ (&add_sel->location,
+ &attr_sel->location) ;
+ } else if (token && (token->type == DELIM_TK)
+ && (token->u.unichar == ':')) {
+ CRPseudo *pseudo = NULL;
+
+ /*try to parse a pseudo */
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ pseudo = cr_pseudo_new ();
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ cr_parsing_location_copy
+ (&pseudo->location,
+ &token->location) ;
+
+ if (token->type == IDENT_TK) {
+ pseudo->type = IDENT_PSEUDO;
+ pseudo->name = token->u.str;
+ token->u.str = NULL;
+ found_sel = TRUE;
+ } else if (token->type == FUNCTION_TK) {
+ pseudo->name = token->u.str;
+ token->u.str = NULL;
+ cr_parser_try_to_skip_spaces_and_comments
+ (a_this);
+ status = cr_parser_parse_ident
+ (a_this, &pseudo->extra);
+
+ ENSURE_PARSING_COND (status == CR_OK);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == ')');
+ pseudo->type = FUNCTION_PSEUDO;
+ found_sel = TRUE;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if (status == CR_OK) {
+ CRAdditionalSel *add_sel = NULL;
+
+ add_sel = cr_additional_sel_new_with_type
+ (PSEUDO_CLASS_ADD_SELECTOR);
+
+ add_sel->content.pseudo = pseudo;
+ cr_parsing_location_copy
+ (&add_sel->location,
+ &pseudo->location) ;
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ status = CR_OK;
+ }
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ break;
+ }
+ }
+
+ if (status == CR_OK && found_sel == TRUE) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ sel->add_sel = add_sel_list;
+ add_sel_list = NULL;
+
+ if (*a_sel == NULL) {
+ *a_sel = sel;
+ } else {
+ cr_simple_sel_append_simple_sel (*a_sel, sel);
+ }
+
+ sel = NULL;
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (add_sel_list) {
+ cr_additional_sel_destroy (add_sel_list);
+ add_sel_list = NULL;
+ }
+
+ if (sel) {
+ cr_simple_sel_destroy (sel);
+ sel = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+
+}
+
+/**
+ * cr_parser_parse_simple_sels:
+ *@a_this: the this pointer of the current instance of #CRParser.
+ *@a_start: a pointer to the
+ *first character of the successfully parsed
+ *string.
+ *@a_end: a pointer to the last character of the successfully parsed
+ *string.
+ *
+ *Parses a "selector" as defined by the css2 spec in appendix D.1:
+ *selector ::= simple_selector [ combinator simple_selector ]*
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_simple_sels (CRParser * a_this,
+ CRSimpleSel ** a_sel)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRSimpleSel *sel = NULL;
+ guint32 cur_char = 0;
+
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_sel,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_simple_selector (a_this, &sel);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ *a_sel = cr_simple_sel_append_simple_sel (*a_sel, sel);
+
+ for (;;) {
+ guint32 next_char = 0;
+ enum Combinator comb = 0;
+
+ sel = NULL;
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == '+') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ comb = COMB_PLUS;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ } else if (next_char == '>') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ comb = COMB_GT;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ } else {
+ comb = COMB_WS;
+ }
+
+ status = cr_parser_parse_simple_selector (a_this, &sel);
+ if (status != CR_OK)
+ break;
+
+ if (comb && sel) {
+ sel->combinator = comb;
+ comb = 0;
+ }
+ if (sel) {
+ *a_sel = cr_simple_sel_append_simple_sel (*a_sel,
+ sel) ;
+ }
+ }
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_selector:
+ *@a_this: the current instance of #CRParser.
+ *@a_selector: the parsed list of comma separated
+ *selectors.
+ *
+ *Parses a comma separated list of selectors.
+ *
+ *Returns CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_selector (CRParser * a_this,
+ CRSelector ** a_selector)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0,
+ next_char = 0;
+ CRSimpleSel *simple_sels = NULL;
+ CRSelector *selector = NULL;
+
+ g_return_val_if_fail (a_this && a_selector, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_simple_sels (a_this, &simple_sels);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ if (simple_sels) {
+ selector = cr_selector_append_simple_sel
+ (selector, simple_sels);
+ if (selector) {
+ cr_parsing_location_copy
+ (&selector->location,
+ &simple_sels->location) ;
+ }
+ simple_sels = NULL;
+ } else {
+ status = CR_PARSING_ERROR ;
+ goto error ;
+ }
+
+ status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr,
+ &next_char);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ goto okay;
+ } else {
+ goto error;
+ }
+ }
+
+ if (next_char == ',') {
+ for (;;) {
+ simple_sels = NULL;
+
+ status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr,
+ &next_char);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ break;
+ } else {
+ goto error;
+ }
+ }
+
+ if (next_char != ',')
+ break;
+
+ /*consume the ',' char */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_simple_sels
+ (a_this, &simple_sels);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ if (simple_sels) {
+ selector =
+ cr_selector_append_simple_sel
+ (selector, simple_sels);
+
+ simple_sels = NULL;
+ }
+ }
+ }
+
+ okay:
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if (!*a_selector) {
+ *a_selector = selector;
+ } else {
+ *a_selector = cr_selector_append (*a_selector, selector);
+ }
+
+ selector = NULL;
+ return CR_OK;
+
+ error:
+
+ if (simple_sels) {
+ cr_simple_sel_destroy (simple_sels);
+ simple_sels = NULL;
+ }
+
+ if (selector) {
+ cr_selector_unref (selector);
+ selector = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_function:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *@a_func_name: out parameter. The parsed function name
+ *@a_expr: out parameter. The successfully parsed term.
+ *
+ *Parses a "function" as defined in css spec at appendix D.1:
+ *function ::= FUNCTION S* expr ')' S*
+ *FUNCTION ::= ident'('
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_function (CRParser * a_this,
+ CRString ** a_func_name,
+ CRTerm ** a_expr)
+{
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ CRToken *token = NULL;
+ CRTerm *expr = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_func_name,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ if (token && token->type == FUNCTION_TK) {
+ *a_func_name = token->u.str;
+ token->u.str = NULL;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this) ;
+
+ status = cr_parser_parse_expr (a_this, &expr);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ ENSURE_PARSING_COND (token && token->type == PC_TK);
+
+ cr_token_destroy (token);
+ token = NULL;
+
+ if (expr) {
+ *a_expr = cr_term_append_term (*a_expr, expr);
+ expr = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (*a_func_name) {
+ cr_string_destroy (*a_func_name);
+ *a_func_name = NULL;
+ }
+
+ if (expr) {
+ cr_term_destroy (expr);
+ expr = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_uri:
+ *@a_this: the current instance of #CRParser.
+ *@a_str: the successfully parsed url.
+ *
+ *Parses an uri as defined by the css spec [4.1.1]:
+ * URI ::= url\({w}{string}{w}\)
+ * |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\)
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_uri (CRParser * a_this, CRString ** a_str)
+{
+
+ enum CRStatus status = CR_PARSING_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr,
+ URI_TK, NO_ET, a_str, NULL);
+ return status;
+}
+
+/**
+ * cr_parser_parse_string:
+ *@a_this: the current instance of #CRParser.
+ *@a_start: out parameter. Upon successful completion,
+ *points to the beginning of the string, points to an undefined value
+ *otherwise.
+ *@a_end: out parameter. Upon successful completion, points to
+ *the beginning of the string, points to an undefined value otherwise.
+ *
+ *Parses a string type as defined in css spec [4.1.1]:
+ *
+ *string ::= {string1}|{string2}
+ *string1 ::= \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\"
+ *string2 ::= \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\'
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_string (CRParser * a_this, CRString ** a_str)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr,
+ STRING_TK, NO_ET, a_str, NULL);
+ return status;
+}
+
+/**
+ *Parses an "ident" as defined in css spec [4.1.1]:
+ *ident ::= {nmstart}{nmchar}*
+ *
+ *@param a_this the currens instance of #CRParser.
+ *
+ *@param a_str a pointer to parsed ident. If *a_str is NULL,
+ *this function allocates a new instance of #CRString. If not,
+ *the function just appends the parsed string to the one passed.
+ *In both cases it is up to the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_ident (CRParser * a_this, CRString ** a_str)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr,
+ IDENT_TK, NO_ET, a_str, NULL);
+ return status;
+}
+
+/**
+ *the next rule is ignored as well. This seems to be a bug
+ *Parses a stylesheet as defined in the css2 spec in appendix D.1:
+ *stylesheet ::= [ CHARSET_SYM S* STRING S* ';' ]?
+ * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
+ * [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]*
+ *
+ *TODO: Finish the code of this function. Think about splitting it into
+ *smaller functions.
+ *
+ *@param a_this the "this pointer" of the current instance of #CRParser.
+ *@param a_start out parameter. A pointer to the first character of
+ *the successfully parsed string.
+ *@param a_end out parameter. A pointer to the first character of
+ *the successfully parsed string.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_stylesheet (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRString *charset = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PRIVATE (a_this)->state = READY_STATE;
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_document) {
+ PRIVATE (a_this)->sac_handler->start_document
+ (PRIVATE (a_this)->sac_handler);
+ }
+
+ parse_charset:
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ if (token && token->type == CHARSET_SYM_TK) {
+ CRParsingLocation location = {0} ;
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+
+ status = cr_parser_parse_charset (a_this,
+ &charset,
+ &location);
+
+ if (status == CR_OK && charset) {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->charset) {
+ PRIVATE (a_this)->sac_handler->charset
+ (PRIVATE (a_this)->sac_handler,
+ charset, &location);
+ }
+ } else if (status != CR_END_OF_INPUT_ERROR) {
+ status = cr_parser_parse_atrule_core (a_this);
+ CHECK_PARSING_STATUS (status, FALSE);
+ }
+
+ if (charset) {
+ cr_string_destroy (charset);
+ charset = NULL;
+ }
+ } else if (token
+ && (token->type == S_TK
+ || token->type == COMMENT_TK)) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto parse_charset ;
+ } else if (token) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+ }
+
+/* parse_imports:*/
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this) ;
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+ } while (token
+ && (token->type == S_TK
+ || token->type == CDO_TK || token->type == CDC_TK));
+
+ if (token) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ }
+
+ for (;;) {
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ if (token && token->type == IMPORT_SYM_TK) {
+ GList *media_list = NULL;
+ CRString *import_string = NULL;
+ CRParsingLocation location = {0} ;
+
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ status = cr_parser_parse_import (a_this,
+ &media_list,
+ &import_string,
+ &location);
+ if (status == CR_OK) {
+ if (import_string
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->import_style) {
+ PRIVATE (a_this)->sac_handler->import_style
+ (PRIVATE(a_this)->sac_handler,
+ media_list,
+ import_string,
+ NULL, &location) ;
+
+ if ((PRIVATE (a_this)->sac_handler->resolve_import == TRUE)) {
+ /*
+ *TODO: resolve the
+ *import rule.
+ */
+ }
+
+ if ((PRIVATE (a_this)->sac_handler->import_style_result)) {
+ PRIVATE (a_this)->sac_handler->import_style_result
+ (PRIVATE (a_this)->sac_handler,
+ media_list, import_string,
+ NULL, NULL);
+ }
+ }
+ } else if (status != CR_END_OF_INPUT_ERROR) {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->error
+ (PRIVATE (a_this)->sac_handler);
+ }
+ status = cr_parser_parse_atrule_core (a_this);
+ CHECK_PARSING_STATUS (status, TRUE) ;
+ } else {
+ goto error ;
+ }
+
+ /*
+ *then, after calling the appropriate
+ *SAC handler, free
+ *the media_list and import_string.
+ */
+ if (media_list) {
+ GList *cur = NULL;
+
+ /*free the medium list */
+ for (cur = media_list; cur; cur = cur->next) {
+ if (cur->data) {
+ cr_string_destroy (cur->data);
+ }
+ }
+
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+
+ if (import_string) {
+ cr_string_destroy (import_string);
+ import_string = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ } else if (token
+ && (token->type == S_TK
+ || token->type == CDO_TK
+ || token->type == CDC_TK)) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+ } while (token
+ && (token->type == S_TK
+ || token->type == CDO_TK
+ || token->type == CDC_TK));
+ } else {
+ if (token) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+ goto parse_ruleset_and_others;
+ }
+ }
+
+ parse_ruleset_and_others:
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ for (;;) {
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ if (token
+ && (token->type == S_TK
+ || token->type == CDO_TK || token->type == CDC_TK)) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments
+ (a_this);
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ } while (token
+ && (token->type == S_TK
+ || token->type == COMMENT_TK
+ || token->type == CDO_TK
+ || token->type == CDC_TK));
+ if (token) {
+ cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+ } else if (token
+ && (token->type == HASH_TK
+ || (token->type == DELIM_TK
+ && token->u.unichar == '.')
+ || (token->type == DELIM_TK
+ && token->u.unichar == ':')
+ || (token->type == DELIM_TK
+ && token->u.unichar == '*')
+ || (token->type == BO_TK)
+ || token->type == IDENT_TK)) {
+ /*
+ *Try to parse a CSS2 ruleset.
+ *if the parsing fails, try to parse
+ *a css core ruleset.
+ */
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+
+ status = cr_parser_parse_ruleset (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_ruleset_core
+ (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else if (token && token->type == MEDIA_SYM_TK) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+
+ status = cr_parser_parse_media (a_this);
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_atrule_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ } else if (token && token->type == PAGE_SYM_TK) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_page (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_atrule_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else if (token && token->type == FONT_FACE_SYM_TK) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_font_face (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_atrule_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_statement_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_END_OF_INPUT_ERROR || status == CR_OK) {
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_document) {
+ PRIVATE (a_this)->sac_handler->end_document
+ (PRIVATE (a_this)->sac_handler);
+ }
+
+ return CR_OK;
+ }
+
+ cr_parser_push_error
+ (a_this, (const guchar *) "could not recognize next production", CR_ERROR);
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->unrecoverable_error) {
+ PRIVATE (a_this)->sac_handler->
+ unrecoverable_error (PRIVATE (a_this)->sac_handler);
+ }
+
+ cr_parser_dump_err_stack (a_this, TRUE);
+
+ return status;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->unrecoverable_error) {
+ PRIVATE (a_this)->sac_handler->
+ unrecoverable_error (PRIVATE (a_this)->sac_handler);
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/****************************************
+ *Public CRParser Methods
+ ****************************************/
+
+/**
+ * cr_parser_new:
+ * @a_tknzr: the tokenizer to use for the parsing.
+ *
+ *Creates a new parser to parse data
+ *coming the input stream given in parameter.
+ *
+ *Returns the newly created instance of #CRParser,
+ *or NULL if an error occurred.
+ */
+CRParser *
+cr_parser_new (CRTknzr * a_tknzr)
+{
+ CRParser *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ result = g_malloc0 (sizeof (CRParser));
+
+ PRIVATE (result) = g_malloc0 (sizeof (CRParserPriv));
+
+ if (a_tknzr) {
+ status = cr_parser_set_tknzr (result, a_tknzr);
+ }
+
+ g_return_val_if_fail (status == CR_OK, NULL);
+
+ return result;
+}
+
+/**
+ * cr_parser_new_from_buf:
+ *@a_buf: the buffer to parse.
+ *@a_len: the length of the data in the buffer.
+ *@a_enc: the encoding of the input buffer a_buf.
+ *@a_free_buf: if set to TRUE, a_buf will be freed
+ *during the destruction of the newly built instance
+ *of #CRParser. If set to FALSE, it is up to the caller to
+ *eventually free it.
+ *
+ *Instantiates a new parser from a memory buffer.
+ *
+ *Returns the newly built parser, or NULL if an error arises.
+ */
+CRParser *
+cr_parser_new_from_buf (guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_buf)
+{
+ CRParser *result = NULL;
+ CRInput *input = NULL;
+
+ g_return_val_if_fail (a_buf && a_len, NULL);
+
+ input = cr_input_new_from_buf (a_buf, a_len, a_enc, a_free_buf);
+ g_return_val_if_fail (input, NULL);
+
+ result = cr_parser_new_from_input (input);
+ if (!result) {
+ cr_input_destroy (input);
+ input = NULL;
+ return NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_parser_new_from_input:
+ * @a_input: the parser input stream to use.
+ *
+ * Returns a newly built parser input.
+ */
+CRParser *
+cr_parser_new_from_input (CRInput * a_input)
+{
+ CRParser *result = NULL;
+ CRTknzr *tokenizer = NULL;
+
+ if (a_input) {
+ tokenizer = cr_tknzr_new (a_input);
+ g_return_val_if_fail (tokenizer, NULL);
+ }
+
+ result = cr_parser_new (tokenizer);
+ g_return_val_if_fail (result, NULL);
+
+ return result;
+}
+
+/**
+ * cr_parser_new_from_file:
+ * @a_file_uri: the uri of the file to parse.
+ * @a_enc: the file encoding to use.
+ *
+ * Returns the newly built parser.
+ */
+CRParser *
+cr_parser_new_from_file (const guchar * a_file_uri, enum CREncoding a_enc)
+{
+ CRParser *result = NULL;
+ CRTknzr *tokenizer = NULL;
+
+ tokenizer = cr_tknzr_new_from_uri (a_file_uri, a_enc);
+ if (!tokenizer) {
+ cr_utils_trace_info ("Could not open input file");
+ return NULL;
+ }
+
+ result = cr_parser_new (tokenizer);
+ g_return_val_if_fail (result, NULL);
+ return result;
+}
+
+/**
+ * cr_parser_set_sac_handler:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_handler: the handler to set.
+ *
+ *Sets a SAC document handler to the parser.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_sac_handler (CRParser * a_this, CRDocHandler * a_handler)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->sac_handler) {
+ cr_doc_handler_unref (PRIVATE (a_this)->sac_handler);
+ }
+
+ PRIVATE (a_this)->sac_handler = a_handler;
+ cr_doc_handler_ref (a_handler);
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_sac_handler:
+ *@a_this: the "this pointer" of the current instance of
+ *#CRParser.
+ *@a_handler: out parameter. The returned handler.
+ *
+ *Gets the SAC document handler.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_parser_get_sac_handler (CRParser * a_this, CRDocHandler ** a_handler)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ *a_handler = PRIVATE (a_this)->sac_handler;
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_set_default_sac_handler:
+ *@a_this: a pointer to the current instance of #CRParser.
+ *
+ *Sets the SAC handler associated to the current instance
+ *of #CRParser to the default SAC handler.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_default_sac_handler (CRParser * a_this)
+{
+ CRDocHandler *default_sac_handler = NULL;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ default_sac_handler = cr_doc_handler_new ();
+
+ cr_doc_handler_set_default_sac_handler (default_sac_handler);
+
+ status = cr_parser_set_sac_handler (a_this, default_sac_handler);
+
+ if (status != CR_OK) {
+ cr_doc_handler_destroy (default_sac_handler);
+ default_sac_handler = NULL;
+ }
+
+ return status;
+}
+
+/**
+ * cr_parser_set_use_core_grammar:
+ * @a_this: the current instance of #CRParser.
+ * @a_use_core_grammar: where to parse against the css core grammar.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_use_core_grammar (CRParser * a_this,
+ gboolean a_use_core_grammar)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->use_core_grammar = a_use_core_grammar;
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_use_core_grammar:
+ * @a_this: the current instance of #CRParser.
+ * @a_use_core_grammar: whether to use the core grammar or not.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_get_use_core_grammar (CRParser const * a_this,
+ gboolean * a_use_core_grammar)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ *a_use_core_grammar = PRIVATE (a_this)->use_core_grammar;
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_parse_file:
+ *@a_this: a pointer to the current instance of #CRParser.
+ *@a_file_uri: the uri to the file to load. For the time being,
+ *@a_enc: the encoding of the file to parse.
+ *only local files are supported.
+ *
+ *Parses a the given in parameter.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_file (CRParser * a_this,
+ const guchar * a_file_uri, enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_ERROR;
+ CRTknzr *tknzr = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_file_uri, CR_BAD_PARAM_ERROR);
+
+ tknzr = cr_tknzr_new_from_uri (a_file_uri, a_enc);
+
+ g_return_val_if_fail (tknzr != NULL, CR_ERROR);
+
+ status = cr_parser_set_tknzr (a_this, tknzr);
+ g_return_val_if_fail (status == CR_OK, CR_ERROR);
+
+ status = cr_parser_parse (a_this);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_expr:
+ * @a_this: the current instance of #CRParser.
+ * @a_expr: out parameter. the parsed expression.
+ *
+ *Parses an expression as defined by the css2 spec in appendix
+ *D.1:
+ *expr: term [ operator term ]*
+ *
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_expr (CRParser * a_this, CRTerm ** a_expr)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRTerm *expr = NULL,
+ *expr2 = NULL;
+ guchar next_byte = 0;
+ gulong nb_terms = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_expr, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_term (a_this, &expr);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ for (;;) {
+ guchar operator = 0;
+
+ status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr,
+ 1, &next_byte);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ /*
+ if (!nb_terms)
+ {
+ goto error ;
+ }
+ */
+ status = CR_OK;
+ break;
+ } else {
+ goto error;
+ }
+ }
+
+ if (next_byte == '/' || next_byte == ',') {
+ READ_NEXT_BYTE (a_this, &operator);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_term (a_this, &expr2);
+
+ if (status != CR_OK || expr2 == NULL) {
+ status = CR_OK;
+ break;
+ }
+
+ switch (operator) {
+ case '/':
+ expr2->the_operator = DIVIDE;
+ break;
+ case ',':
+ expr2->the_operator = COMMA;
+
+ default:
+ break;
+ }
+
+ expr = cr_term_append_term (expr, expr2);
+ expr2 = NULL;
+ operator = 0;
+ nb_terms++;
+ }
+
+ if (status == CR_OK) {
+ *a_expr = cr_term_append_term (*a_expr, expr);
+ expr = NULL;
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+ }
+
+ error:
+
+ if (expr) {
+ cr_term_destroy (expr);
+ expr = NULL;
+ }
+
+ if (expr2) {
+ cr_term_destroy (expr2);
+ expr2 = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_prio:
+ *@a_this: the current instance of #CRParser.
+ *@a_prio: a string representing the priority.
+ *Today, only "!important" is returned as only this
+ *priority is defined by css2.
+ *
+ *Parses a declaration priority as defined by
+ *the css2 grammar in appendix C:
+ *prio: IMPORTANT_SYM S*
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_prio (CRParser * a_this, CRString ** a_prio)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prio
+ && *a_prio == NULL, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR) {
+ goto error;
+ }
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == IMPORTANT_SYM_TK);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ *a_prio = cr_string_new_from_string ("!important");
+ cr_token_destroy (token);
+ token = NULL;
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_declaration:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_property: the successfully parsed property. The caller
+ * *must* free the returned pointer.
+ *@a_expr: the expression that represents the attribute value.
+ *The caller *must* free the returned pointer.
+ *
+ *TODO: return the parsed priority, so that
+ *upper layers can take benefit from it.
+ *Parses a "declaration" as defined by the css2 spec in appendix D.1:
+ *declaration ::= [property ':' S* expr prio?]?
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_declaration (CRParser * a_this,
+ CRString ** a_property,
+ CRTerm ** a_expr, gboolean * a_important)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ guint32 cur_char = 0;
+ CRTerm *expr = NULL;
+ CRString *prio = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_property && a_expr
+ && a_important, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_property (a_this, a_property);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto error;
+
+ CHECK_PARSING_STATUS_ERR
+ (a_this, status, FALSE,
+ (const guchar *) "while parsing declaration: next property is malformed",
+ CR_SYNTAX_ERROR);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != ':') {
+ status = CR_PARSING_ERROR;
+ cr_parser_push_error
+ (a_this,
+ (const guchar *) "while parsing declaration: this char must be ':'",
+ CR_SYNTAX_ERROR);
+ goto error;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_expr (a_this, &expr);
+
+ CHECK_PARSING_STATUS_ERR
+ (a_this, status, FALSE,
+ (const guchar *) "while parsing declaration: next expression is malformed",
+ CR_SYNTAX_ERROR);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_prio (a_this, &prio);
+ if (prio) {
+ cr_string_destroy (prio);
+ prio = NULL;
+ *a_important = TRUE;
+ } else {
+ *a_important = FALSE;
+ }
+ if (*a_expr) {
+ cr_term_append_term (*a_expr, expr);
+ expr = NULL;
+ } else {
+ *a_expr = expr;
+ expr = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (expr) {
+ cr_term_destroy (expr);
+ expr = NULL;
+ }
+
+ if (*a_property) {
+ cr_string_destroy (*a_property);
+ *a_property = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_statement_core:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Parses a statement as defined by the css core grammar in
+ *chapter 4.1 of the css2 spec.
+ *statement : ruleset | at-rule;
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_statement_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ switch (token->type) {
+ case ATKEYWORD_TK:
+ case IMPORT_SYM_TK:
+ case PAGE_SYM_TK:
+ case MEDIA_SYM_TK:
+ case FONT_FACE_SYM_TK:
+ case CHARSET_SYM_TK:
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_atrule_core (a_this);
+ CHECK_PARSING_STATUS (status, TRUE);
+ break;
+
+ default:
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_ruleset_core (a_this);
+ cr_parser_clear_errors (a_this);
+ CHECK_PARSING_STATUS (status, TRUE);
+ }
+
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_ruleset:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *Parses a "ruleset" as defined in the css2 spec at appendix D.1.
+ *ruleset ::= selector [ ',' S* selector ]*
+ *'{' S* declaration? [ ';' S* declaration? ]* '}' S*;
+ *
+ *This methods calls the the SAC handler on the relevant SAC handler
+ *callbacks whenever it encounters some specific constructions.
+ *See the documentation of #CRDocHandler (the SAC handler) to know
+ *when which SAC handler is called.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_ruleset (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0,
+ next_char = 0;
+ CRString *property = NULL;
+ CRTerm *expr = NULL;
+ CRSimpleSel *simple_sels = NULL;
+ CRSelector *selector = NULL;
+ gboolean start_selector = FALSE,
+ is_important = FALSE;
+ CRParsingLocation end_parsing_location;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_selector (a_this, &selector);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ ENSURE_PARSING_COND_ERR
+ (a_this, cur_char == '{',
+ (const guchar *) "while parsing rulset: current char should be '{'",
+ CR_SYNTAX_ERROR);
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_selector) {
+ /*
+ *the selector is ref counted so that the parser's user
+ *can choose to keep it.
+ */
+ if (selector) {
+ cr_selector_ref (selector);
+ }
+
+ PRIVATE (a_this)->sac_handler->start_selector
+ (PRIVATE (a_this)->sac_handler, selector);
+ start_selector = TRUE;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_RULESET_STATE;
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &expr,
+ &is_important);
+ if (expr) {
+ cr_term_ref (expr);
+ }
+ if (status == CR_OK
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler, property, expr,
+ is_important);
+ }
+ if (status == CR_OK) {
+ /*
+ *free the allocated
+ *'property' and 'term' before parsing
+ *next declarations.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+ } else {/*status != CR_OK*/
+ guint32 c = 0 ;
+ /*
+ *test if we have reached '}', which
+ *would mean that we are parsing an empty ruleset (eg. x{ })
+ *In that case, goto end_of_ruleset.
+ */
+ status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, &c) ;
+ if (status == CR_OK && c == '}') {
+ status = CR_OK ;
+ goto end_of_ruleset ;
+ }
+ }
+ CHECK_PARSING_STATUS_ERR
+ (a_this, status, FALSE,
+ (const guchar *) "while parsing ruleset: next construction should be a declaration",
+ CR_SYNTAX_ERROR);
+
+ for (;;) {
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char != ';')
+ break;
+
+ /*consume the ';' char */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &expr, &is_important);
+
+ if (expr) {
+ cr_term_ref (expr);
+ }
+ if (status == CR_OK
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, expr, is_important);
+ }
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+ }
+
+ end_of_ruleset:
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ cr_parser_get_parsing_location (a_this, &end_parsing_location);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND_ERR
+ (a_this, cur_char == '}',
+ (const guchar *) "while parsing rulset: current char must be a '}'",
+ CR_SYNTAX_ERROR);
+
+ selector->location = end_parsing_location;
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_selector) {
+ PRIVATE (a_this)->sac_handler->end_selector
+ (PRIVATE (a_this)->sac_handler, selector);
+ start_selector = FALSE;
+ }
+
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+
+ if (simple_sels) {
+ cr_simple_sel_destroy (simple_sels);
+ simple_sels = NULL;
+ }
+
+ if (selector) {
+ cr_selector_unref (selector);
+ selector = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = RULESET_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+ if (start_selector == TRUE
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->error
+ (PRIVATE (a_this)->sac_handler);
+ }
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+ if (simple_sels) {
+ cr_simple_sel_destroy (simple_sels);
+ simple_sels = NULL;
+ }
+ if (property) {
+ cr_string_destroy (property);
+ }
+ if (selector) {
+ cr_selector_unref (selector);
+ selector = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_import:
+ *@a_this: the "this pointer" of the current instance
+ *of #CRParser.
+ *@a_media_list: out parameter. A linked list of
+ *#CRString
+ *Each CRString is a string that contains
+ *a 'medium' declaration part of the successfully
+ *parsed 'import' declaration.
+ *@a_import_string: out parameter.
+ *A string that contains the 'import
+ *string". The import string can be either an uri (if it starts with
+ *the substring "uri(") or a any other css2 string. Note that
+ * *a_import_string must be initially set to NULL or else, this function
+ *will return CR_BAD_PARAM_ERROR.
+ *@a_location: the location (line, column) where the import has been parsed
+ *
+ *Parses an 'import' declaration as defined in the css2 spec
+ *in appendix D.1:
+ *
+ *import ::=
+ *\@import [STRING|URI] S* [ medium [ ',' S* medium]* ]? ';' S*
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_import (CRParser * a_this,
+ GList ** a_media_list,
+ CRString ** a_import_string,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0,
+ next_char = 0;
+ CRString *medium = NULL;
+
+ g_return_val_if_fail (a_this
+ && a_import_string
+ && (*a_import_string == NULL),
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ if (BYTE (a_this, 1, NULL) == '@'
+ && BYTE (a_this, 2, NULL) == 'i'
+ && BYTE (a_this, 3, NULL) == 'm'
+ && BYTE (a_this, 4, NULL) == 'p'
+ && BYTE (a_this, 5, NULL) == 'o'
+ && BYTE (a_this, 6, NULL) == 'r'
+ && BYTE (a_this, 7, NULL) == 't') {
+ SKIP_CHARS (a_this, 1);
+ if (a_location) {
+ cr_parser_get_parsing_location
+ (a_this, a_location) ;
+ }
+ SKIP_CHARS (a_this, 6);
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_IMPORT_STATE;
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == '"' || next_char == '\'') {
+ status = cr_parser_parse_string (a_this, a_import_string);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else {
+ status = cr_parser_parse_uri (a_this, a_import_string);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_ident (a_this, &medium);
+
+ if (status == CR_OK && medium) {
+ *a_media_list = g_list_append (*a_media_list, medium);
+ medium = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ for (; status == CR_OK;) {
+ if ((status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr,
+ &next_char)) != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ goto okay;
+ }
+ goto error;
+ }
+
+ if (next_char == ',') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ } else {
+ break;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_ident (a_this, &medium);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if ((status == CR_OK) && medium) {
+ *a_media_list = g_list_append (*a_media_list, medium);
+
+ medium = NULL;
+ }
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == ';');
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ okay:
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = IMPORT_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+
+ if (*a_media_list) {
+ GList *cur = NULL;
+
+ /*
+ *free each element of *a_media_list.
+ *Note that each element of *a_medium list *must*
+ *be a GString* or else, the code that is coming next
+ *will corrupt the memory and lead to hard to debug
+ *random crashes.
+ *This is where C++ and its compile time
+ *type checking mechanism (through STL containers) would
+ *have prevented us to go through this hassle.
+ */
+ for (cur = *a_media_list; cur; cur = cur->next) {
+ if (cur->data) {
+ cr_string_destroy (cur->data);
+ }
+ }
+
+ g_list_free (*a_media_list);
+ *a_media_list = NULL;
+ }
+
+ if (*a_import_string) {
+ cr_string_destroy (*a_import_string);
+ *a_import_string = NULL;
+ }
+
+ if (medium) {
+ cr_string_destroy (medium);
+ medium = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_media:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *Parses a 'media' declaration as specified in the css2 spec at
+ *appendix D.1:
+ *
+ *media ::= \@media S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S*
+ *
+ *Note that this function calls the required sac handlers during the parsing
+ *to notify media productions. See #CRDocHandler to know the callback called
+ *during \@media parsing.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_media (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ guint32 next_char = 0,
+ cur_char = 0;
+ CRString *medium = NULL;
+ GList *media_list = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this),
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == MEDIA_SYM_TK);
+ cr_parsing_location_copy (&location, &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == IDENT_TK);
+
+ medium = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+
+ if (medium) {
+ media_list = g_list_append (media_list, medium);
+ medium = NULL;
+ }
+
+ for (; status == CR_OK;) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == ',') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ } else {
+ break;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_ident (a_this, &medium);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ if (medium) {
+ media_list = g_list_append (media_list, medium);
+ medium = NULL;
+ }
+ }
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ ENSURE_PARSING_COND (cur_char == '{');
+
+ /*
+ *call the SAC handler api here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_media) {
+ PRIVATE (a_this)->sac_handler->start_media
+ (PRIVATE (a_this)->sac_handler, media_list,
+ &location);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_MEDIA_STATE;
+
+ for (; status == CR_OK;) {
+ status = cr_parser_parse_ruleset (a_this);
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ }
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ ENSURE_PARSING_COND (cur_char == '}');
+
+ /*
+ *call the right SAC handler api here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_media) {
+ PRIVATE (a_this)->sac_handler->end_media
+ (PRIVATE (a_this)->sac_handler, media_list);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ /*
+ *Then, free the data structures passed to
+ *the last call to the SAC handler.
+ */
+ if (medium) {
+ cr_string_destroy (medium);
+ medium = NULL;
+ }
+
+ if (media_list) {
+ GList *cur = NULL;
+
+ for (cur = media_list; cur; cur = cur->next) {
+ cr_string_destroy (cur->data);
+ }
+
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = MEDIA_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (medium) {
+ cr_string_destroy (medium);
+ medium = NULL;
+ }
+
+ if (media_list) {
+ GList *cur = NULL;
+
+ for (cur = media_list; cur; cur = cur->next) {
+ cr_string_destroy (cur->data);
+ }
+
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_page:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *Parses '\@page' rule as specified in the css2 spec in appendix D.1:
+ *page ::= PAGE_SYM S* IDENT? pseudo_page? S*
+ *'{' S* declaration [ ';' S* declaration ]* '}' S*
+ *
+ *This function also calls the relevant SAC handlers whenever it
+ *encounters a construction that must
+ *be reported to the calling application.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_page (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRTerm *css_expression = NULL;
+ CRString *page_selector = NULL,
+ *page_pseudo_class = NULL,
+ *property = NULL;
+ gboolean important = TRUE;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token) ;
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == PAGE_SYM_TK);
+
+ cr_parsing_location_copy (&location, &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == IDENT_TK) {
+ page_selector = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+ } else {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+
+ /*
+ *try to parse pseudo_page
+ */
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == DELIM_TK && token->u.unichar == ':') {
+ cr_token_destroy (token);
+ token = NULL;
+ status = cr_parser_parse_ident (a_this, &page_pseudo_class);
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+
+ /*
+ *parse_block
+ *
+ */
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+
+ cr_token_destroy (token);
+ token = NULL;
+
+ /*
+ *Call the appropriate SAC handler here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_page) {
+ PRIVATE (a_this)->sac_handler->start_page
+ (PRIVATE (a_this)->sac_handler,
+ page_selector, page_pseudo_class,
+ &location);
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_PAGE_STATE;
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &css_expression,
+ &important);
+ ENSURE_PARSING_COND (status == CR_OK);
+
+ /*
+ *call the relevant SAC handler here...
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ if (css_expression)
+ cr_term_ref (css_expression);
+
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ /*
+ *... and free the data structure passed to that last
+ *SAC handler.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+
+ for (;;) {
+ /*parse the other ';' separated declarations */
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type != SEMICOLON_TK) {
+ cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL ;
+ break;
+ }
+
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &css_expression,
+ &important);
+ if (status != CR_OK)
+ break ;
+
+ /*
+ *call the relevant SAC handler here...
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ cr_term_ref (css_expression);
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ /*
+ *... and free the data structure passed to that last
+ *SAC handler.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+ }
+ cr_parser_try_to_skip_spaces_and_comments
+ (a_this) ;
+ if (token) {
+ cr_token_destroy (token) ;
+ token = NULL ;
+ }
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == CBC_TK) ;
+ cr_token_destroy (token) ;
+ token = NULL ;
+ /*
+ *call the relevant SAC handler here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_page) {
+ PRIVATE (a_this)->sac_handler->end_page
+ (PRIVATE (a_this)->sac_handler,
+ page_selector, page_pseudo_class);
+ }
+
+ if (page_selector) {
+ cr_string_destroy (page_selector);
+ page_selector = NULL;
+ }
+
+ if (page_pseudo_class) {
+ cr_string_destroy (page_pseudo_class);
+ page_pseudo_class = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ /*here goes the former implem of this function ... */
+
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = PAGE_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ if (page_selector) {
+ cr_string_destroy (page_selector);
+ page_selector = NULL;
+ }
+ if (page_pseudo_class) {
+ cr_string_destroy (page_pseudo_class);
+ page_pseudo_class = NULL;
+ }
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_destroy (css_expression);
+ css_expression = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+ return status;
+}
+
+/**
+ * cr_parser_parse_charset:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_value: out parameter. The actual parsed value of the charset
+ *declararation. Note that for safety check reasons, *a_value must be
+ *set to NULL.
+ *@a_charset_sym_location: the parsing location of the charset rule
+ *
+ *Parses a charset declaration as defined implicitly by the css2 spec in
+ *appendix D.1:
+ *charset ::= CHARSET_SYM S* STRING S* ';'
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_charset (CRParser * a_this, CRString ** a_value,
+ CRParsingLocation *a_charset_sym_location)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRString *charset_str = NULL;
+
+ g_return_val_if_fail (a_this && a_value
+ && (*a_value == NULL),
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == CHARSET_SYM_TK);
+ if (a_charset_sym_location) {
+ cr_parsing_location_copy (a_charset_sym_location,
+ &token->location) ;
+ }
+ cr_token_destroy (token);
+ token = NULL;
+
+ PRIVATE (a_this)->state = TRY_PARSE_CHARSET_STATE;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == STRING_TK);
+ charset_str = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == SEMICOLON_TK);
+ cr_token_destroy (token);
+ token = NULL;
+
+ if (charset_str) {
+ *a_value = charset_str;
+ charset_str = NULL;
+ }
+
+ PRIVATE (a_this)->state = CHARSET_PARSED_STATE;
+ return CR_OK;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (*a_value) {
+ cr_string_destroy (*a_value);
+ *a_value = NULL;
+ }
+
+ if (charset_str) {
+ cr_string_destroy (charset_str);
+ charset_str = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_font_face:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Parses the "\@font-face" rule specified in the css1 spec in
+ *appendix D.1:
+ *
+ *font_face ::= FONT_FACE_SYM S*
+ *'{' S* declaration [ ';' S* declaration ]* '}' S*
+ *
+ *This function will call SAC handlers whenever it is necessary.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_font_face (CRParser * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRString *property = NULL;
+ CRTerm *css_expression = NULL;
+ CRToken *token = NULL;
+ gboolean important = FALSE;
+ guint32 next_char = 0,
+ cur_char = 0;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == FONT_FACE_SYM_TK);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ if (token) {
+ cr_parsing_location_copy (&location,
+ &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ /*
+ *here, call the relevant SAC handler.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_font_face) {
+ PRIVATE (a_this)->sac_handler->start_font_face
+ (PRIVATE (a_this)->sac_handler, &location);
+ }
+ PRIVATE (a_this)->state = TRY_PARSE_FONT_FACE_STATE;
+ /*
+ *and resume the parsing.
+ */
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration (a_this, &property,
+ &css_expression, &important);
+ if (status == CR_OK) {
+ /*
+ *here, call the relevant SAC handler.
+ */
+ cr_term_ref (css_expression);
+ if (PRIVATE (a_this)->sac_handler &&
+ PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ ENSURE_PARSING_COND (css_expression && property);
+ }
+ /*free the data structures allocated during last parsing. */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+ for (;;) {
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == ';') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ } else {
+ break;
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration (a_this,
+ &property,
+ &css_expression,
+ &important);
+ if (status != CR_OK)
+ break;
+ /*
+ *here, call the relevant SAC handler.
+ */
+ cr_term_ref (css_expression);
+ if (PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ /*
+ *Then, free the data structures allocated during
+ *last parsing.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '}');
+ /*
+ *here, call the relevant SAC handler.
+ */
+ if (PRIVATE (a_this)->sac_handler->end_font_face) {
+ PRIVATE (a_this)->sac_handler->end_font_face
+ (PRIVATE (a_this)->sac_handler);
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = FONT_FACE_PARSED_STATE;
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_destroy (css_expression);
+ css_expression = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+ return status;
+}
+
+/**
+ * cr_parser_parse:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Parses the data that comes from the
+ *input previously associated to the current instance of
+ *#CRParser.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse (CRParser * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->use_core_grammar == FALSE) {
+ status = cr_parser_parse_stylesheet (a_this);
+ } else {
+ status = cr_parser_parse_stylesheet_core (a_this);
+ }
+
+ return status;
+}
+
+/**
+ * cr_parser_set_tknzr:
+ * @a_this: the current instance of #CRParser;
+ * @a_tknzr: the new tokenizer.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_tknzr (CRParser * a_this, CRTknzr * a_tknzr)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->tknzr) {
+ cr_tknzr_unref (PRIVATE (a_this)->tknzr);
+ }
+
+ PRIVATE (a_this)->tknzr = a_tknzr;
+
+ if (a_tknzr)
+ cr_tknzr_ref (a_tknzr);
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_tknzr:
+ *@a_this: the current instance of #CRParser
+ *@a_tknzr: out parameter. The returned tokenizer
+ *
+ *Getter of the parser's underlying tokenizer
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise
+ */
+enum CRStatus
+cr_parser_get_tknzr (CRParser * a_this, CRTknzr ** a_tknzr)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_tknzr, CR_BAD_PARAM_ERROR);
+
+ *a_tknzr = PRIVATE (a_this)->tknzr;
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_parsing_location:
+ *@a_this: the current instance of #CRParser
+ *@a_loc: the parsing location to get.
+ *
+ *Gets the current parsing location.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_parser_get_parsing_location (CRParser const *a_this,
+ CRParsingLocation *a_loc)
+{
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_loc, CR_BAD_PARAM_ERROR) ;
+
+ return cr_tknzr_get_parsing_location
+ (PRIVATE (a_this)->tknzr, a_loc) ;
+}
+
+/**
+ * cr_parser_parse_buf:
+ *@a_this: the current instance of #CRparser
+ *@a_buf: the input buffer
+ *@a_len: the length of the input buffer
+ *@a_enc: the encoding of the buffer
+ *
+ *Parses a stylesheet from a buffer
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_buf (CRParser * a_this,
+ const guchar * a_buf,
+ gulong a_len, enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_ERROR;
+ CRTknzr *tknzr = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_buf, CR_BAD_PARAM_ERROR);
+
+ tknzr = cr_tknzr_new_from_buf ((guchar*)a_buf, a_len, a_enc, FALSE);
+
+ g_return_val_if_fail (tknzr != NULL, CR_ERROR);
+
+ status = cr_parser_set_tknzr (a_this, tknzr);
+ g_return_val_if_fail (status == CR_OK, CR_ERROR);
+
+ status = cr_parser_parse (a_this);
+
+ return status;
+}
+
+/**
+ * cr_parser_destroy:
+ *@a_this: the current instance of #CRParser to
+ *destroy.
+ *
+ *Destroys the current instance
+ *of #CRParser.
+ */
+void
+cr_parser_destroy (CRParser * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ if (PRIVATE (a_this)->tknzr) {
+ if (cr_tknzr_unref (PRIVATE (a_this)->tknzr) == TRUE)
+ PRIVATE (a_this)->tknzr = NULL;
+ }
+
+ if (PRIVATE (a_this)->sac_handler) {
+ cr_doc_handler_unref (PRIVATE (a_this)->sac_handler);
+ PRIVATE (a_this)->sac_handler = NULL;
+ }
+
+ if (PRIVATE (a_this)->err_stack) {
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->err_stack = NULL;
+ }
+
+ if (PRIVATE (a_this)) {
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ a_this = NULL; /*useless. Just for the sake of coherence */
+ }
+}
diff --git a/src/st/croco/cr-parser.h b/src/st/croco/cr-parser.h
new file mode 100644
index 0000000..6dce943
--- /dev/null
+++ b/src/st/croco/cr-parser.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#ifndef __CR_PARSER_H__
+#define __CR_PARSER_H__
+
+#include <glib.h>
+#include "cr-input.h"
+#include "cr-tknzr.h"
+#include "cr-utils.h"
+#include "cr-doc-handler.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration file
+ *of the #CRParser class.
+ */
+typedef struct _CRParser CRParser ;
+typedef struct _CRParserPriv CRParserPriv ;
+
+
+/**
+ *The implementation of
+ *the SAC parser.
+ *The Class is opaque
+ *and must be manipulated through
+ *the provided methods.
+ */
+struct _CRParser {
+ CRParserPriv *priv ;
+} ;
+
+
+CRParser * cr_parser_new (CRTknzr *a_tknzr) ;
+
+CRParser * cr_parser_new_from_buf (guchar *a_buf, gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_buf) ;
+
+CRParser * cr_parser_new_from_file (const guchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+CRParser * cr_parser_new_from_input (CRInput *a_input) ;
+
+enum CRStatus cr_parser_set_tknzr (CRParser *a_this, CRTknzr *a_tknzr) ;
+
+enum CRStatus cr_parser_get_tknzr (CRParser *a_this, CRTknzr **a_tknzr) ;
+
+enum CRStatus cr_parser_get_parsing_location (CRParser const *a_this, CRParsingLocation *a_loc) ;
+
+enum CRStatus cr_parser_try_to_skip_spaces_and_comments (CRParser *a_this) ;
+
+
+enum CRStatus cr_parser_set_sac_handler (CRParser *a_this,
+ CRDocHandler *a_handler) ;
+
+enum CRStatus cr_parser_get_sac_handler (CRParser *a_this,
+ CRDocHandler **a_handler) ;
+
+enum CRStatus cr_parser_set_use_core_grammar (CRParser *a_this,
+ gboolean a_use_core_grammar) ;
+enum CRStatus cr_parser_get_use_core_grammar (CRParser const *a_this,
+ gboolean *a_use_core_grammar) ;
+
+enum CRStatus cr_parser_parse (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_file (CRParser *a_this,
+ const guchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+enum CRStatus cr_parser_parse_buf (CRParser *a_this, const guchar *a_buf,
+ gulong a_len, enum CREncoding a_enc) ;
+
+enum CRStatus cr_parser_set_default_sac_handler (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_term (CRParser *a_this, CRTerm **a_term) ;
+
+enum CRStatus cr_parser_parse_expr (CRParser *a_this, CRTerm **a_expr) ;
+
+enum CRStatus cr_parser_parse_prio (CRParser *a_this, CRString **a_prio) ;
+
+enum CRStatus cr_parser_parse_declaration (CRParser *a_this, CRString **a_property,
+ CRTerm **a_expr, gboolean *a_important) ;
+
+enum CRStatus cr_parser_parse_statement_core (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_ruleset (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_import (CRParser *a_this, GList ** a_media_list,
+ CRString **a_import_string,
+ CRParsingLocation *a_location) ;
+
+enum CRStatus cr_parser_parse_media (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_page (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_charset (CRParser *a_this, CRString **a_value,
+ CRParsingLocation *a_charset_sym_location) ;
+
+enum CRStatus cr_parser_parse_font_face (CRParser *a_this) ;
+
+void cr_parser_destroy (CRParser *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_PARSER_H__*/
diff --git a/src/st/croco/cr-parsing-location.c b/src/st/croco/cr-parsing-location.c
new file mode 100644
index 0000000..2b40974
--- /dev/null
+++ b/src/st/croco/cr-parsing-location.c
@@ -0,0 +1,171 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-parsing-location.h"
+
+/**
+ *@CRParsingLocation:
+ *
+ *Definition of the #CRparsingLocation class.
+ */
+
+
+/**
+ * cr_parsing_location_new:
+ *Instantiates a new parsing location.
+ *
+ *Returns the newly instantiated #CRParsingLocation.
+ *Must be freed by cr_parsing_location_destroy()
+ */
+CRParsingLocation *
+cr_parsing_location_new (void)
+{
+ CRParsingLocation * result = NULL ;
+
+ result = g_try_malloc (sizeof (CRParsingLocation)) ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory error") ;
+ return NULL ;
+ }
+ cr_parsing_location_init (result) ;
+ return result ;
+}
+
+/**
+ * cr_parsing_location_init:
+ *@a_this: the current instance of #CRParsingLocation.
+ *
+ *Initializes the an instance of #CRparsingLocation.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parsing_location_init (CRParsingLocation *a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+
+ memset (a_this, 0, sizeof (CRParsingLocation)) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_parsing_location_copy:
+ *@a_to: the destination of the copy.
+ *Must be allocated by the caller.
+ *@a_from: the source of the copy.
+ *
+ *Copies an instance of CRParsingLocation into another one.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_parsing_location_copy (CRParsingLocation *a_to,
+ CRParsingLocation const *a_from)
+{
+ g_return_val_if_fail (a_to && a_from, CR_BAD_PARAM_ERROR) ;
+
+ memcpy (a_to, a_from, sizeof (CRParsingLocation)) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_parsing_location_to_string:
+ *@a_this: the current instance of #CRParsingLocation.
+ *@a_mask: a bitmap that defines which parts of the
+ *parsing location are to be serialized (line, column or byte offset)
+ *
+ *Returns the serialized string or NULL in case of an error.
+ */
+gchar *
+cr_parsing_location_to_string (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask)
+{
+ GString *result = NULL ;
+ gchar *str = NULL ;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ if (!a_mask) {
+ a_mask = DUMP_LINE | DUMP_COLUMN | DUMP_BYTE_OFFSET ;
+ }
+ result =g_string_new (NULL) ;
+ if (!result)
+ return NULL ;
+ if (a_mask & DUMP_LINE) {
+ g_string_append_printf (result, "line:%d ",
+ a_this->line) ;
+ }
+ if (a_mask & DUMP_COLUMN) {
+ g_string_append_printf (result, "column:%d ",
+ a_this->column) ;
+ }
+ if (a_mask & DUMP_BYTE_OFFSET) {
+ g_string_append_printf (result, "byte offset:%d ",
+ a_this->byte_offset) ;
+ }
+ if (result->len) {
+ str = g_string_free (result, FALSE) ;
+ } else {
+ g_string_free (result, TRUE) ;
+ }
+ return str ;
+}
+
+/**
+ * cr_parsing_location_dump:
+ * @a_this: current instance of #CRParsingLocation
+ * @a_mask: the serialization mask.
+ * @a_fp: the file pointer to dump the parsing location to.
+ */
+void
+cr_parsing_location_dump (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask,
+ FILE *a_fp)
+{
+ gchar *str = NULL ;
+
+ g_return_if_fail (a_this && a_fp) ;
+ str = cr_parsing_location_to_string (a_this, a_mask) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_parsing_location_destroy:
+ *@a_this: the current instance of #CRParsingLocation. Must
+ *have been allocated with cr_parsing_location_new().
+ *
+ *Destroys the current instance of #CRParsingLocation
+ */
+void
+cr_parsing_location_destroy (CRParsingLocation *a_this)
+{
+ g_return_if_fail (a_this) ;
+ g_free (a_this) ;
+}
+
diff --git a/src/st/croco/cr-parsing-location.h b/src/st/croco/cr-parsing-location.h
new file mode 100644
index 0000000..b8064a5
--- /dev/null
+++ b/src/st/croco/cr-parsing-location.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_PARSING_LOCATION_H__
+#define __CR_PARSING_LOCATION_H__
+
+#include "cr-utils.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the CRParsingLocation
+ *object. This object keeps track of line/column/byte offset/
+ *at which the parsing of a given CSS construction appears.
+ */
+
+typedef struct _CRParsingLocation CRParsingLocation;
+struct _CRParsingLocation {
+ guint line ;
+ guint column ;
+ guint byte_offset ;
+} ;
+
+
+enum CRParsingLocationSerialisationMask {
+ DUMP_LINE = 1,
+ DUMP_COLUMN = 1 << 1,
+ DUMP_BYTE_OFFSET = 1 << 2
+} ;
+
+CRParsingLocation * cr_parsing_location_new (void) ;
+
+enum CRStatus cr_parsing_location_init (CRParsingLocation *a_this) ;
+
+enum CRStatus cr_parsing_location_copy (CRParsingLocation *a_to,
+ CRParsingLocation const *a_from) ;
+
+gchar * cr_parsing_location_to_string (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask) ;
+void cr_parsing_location_dump (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask,
+ FILE *a_fp) ;
+
+void cr_parsing_location_destroy (CRParsingLocation *a_this) ;
+
+
+
+G_END_DECLS
+#endif
diff --git a/src/st/croco/cr-prop-list.c b/src/st/croco/cr-prop-list.c
new file mode 100644
index 0000000..03c4478
--- /dev/null
+++ b/src/st/croco/cr-prop-list.c
@@ -0,0 +1,404 @@
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#include <string.h>
+#include "cr-prop-list.h"
+
+#define PRIVATE(a_obj) (a_obj)->priv
+
+struct _CRPropListPriv {
+ CRString *prop;
+ CRDeclaration *decl;
+ CRPropList *next;
+ CRPropList *prev;
+};
+
+static CRPropList *cr_prop_list_allocate (void);
+
+/**
+ *Default allocator of CRPropList
+ *@return the newly allocated CRPropList or NULL
+ *if an error arises.
+ */
+static CRPropList *
+cr_prop_list_allocate (void)
+{
+ CRPropList *result = NULL;
+
+ result = g_try_malloc (sizeof (CRPropList));
+ if (!result) {
+ cr_utils_trace_info ("could not allocate CRPropList");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRPropList));
+ PRIVATE (result) = g_try_malloc (sizeof (CRPropListPriv));
+ if (!result) {
+ cr_utils_trace_info ("could not allocate CRPropListPriv");
+ g_free (result);
+ return NULL;
+ }
+ memset (PRIVATE (result), 0, sizeof (CRPropListPriv));
+ return result;
+}
+
+/****************
+ *public methods
+ ***************/
+
+/**
+ * cr_prop_list_append:
+ *@a_this: the current instance of #CRPropList
+ *@a_to_append: the property list to append
+ *
+ *Appends a property list to the current one.
+ *
+ *Returns the resulting prop list, or NULL if an error
+ *occurred
+ */
+CRPropList *
+cr_prop_list_append (CRPropList * a_this, CRPropList * a_to_append)
+{
+ CRPropList *cur = NULL;
+
+ g_return_val_if_fail (a_to_append, NULL);
+
+ if (!a_this)
+ return a_to_append;
+
+ /*go fetch the last element of the list */
+ for (cur = a_this;
+ cur && PRIVATE (cur) && PRIVATE (cur)->next;
+ cur = PRIVATE (cur)->next) ;
+ g_return_val_if_fail (cur, NULL);
+ PRIVATE (cur)->next = a_to_append;
+ PRIVATE (a_to_append)->prev = cur;
+ return a_this;
+}
+
+/**
+ * cr_prop_list_append2:
+ *Appends a pair of prop/declaration to
+ *the current prop list.
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: the property to consider
+ *@a_decl: the declaration to consider
+ *
+ *Returns the resulting property list, or NULL in case
+ *of an error.
+ */
+CRPropList *
+cr_prop_list_append2 (CRPropList * a_this,
+ CRString * a_prop,
+ CRDeclaration * a_decl)
+{
+ CRPropList *list = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_prop && a_decl, NULL);
+
+ list = cr_prop_list_allocate ();
+ g_return_val_if_fail (list && PRIVATE (list), NULL);
+
+ PRIVATE (list)->prop = a_prop;
+ PRIVATE (list)->decl = a_decl;
+
+ result = cr_prop_list_append (a_this, list);
+ return result;
+}
+
+/**
+ * cr_prop_list_prepend:
+ *@a_this: the current instance of #CRPropList
+ *@a_to_prepend: the new list to prepend.
+ *
+ *Prepends a list to the current list
+ *Returns the new properties list.
+ */
+CRPropList *
+cr_prop_list_prepend (CRPropList * a_this, CRPropList * a_to_prepend)
+{
+ CRPropList *cur = NULL;
+
+ g_return_val_if_fail (a_to_prepend, NULL);
+
+ if (!a_this)
+ return a_to_prepend;
+
+ for (cur = a_to_prepend; cur && PRIVATE (cur)->next;
+ cur = PRIVATE (cur)->next) ;
+ g_return_val_if_fail (cur, NULL);
+ PRIVATE (cur)->next = a_this;
+ PRIVATE (a_this)->prev = cur;
+ return a_to_prepend;
+}
+
+/**
+ * cr_prop_list_prepend2:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop_name: property name to append
+ *@a_decl: the property value to append.
+ *
+ *Prepends a property to a list of properties
+ *
+ *Returns the new property list.
+ */
+CRPropList *
+cr_prop_list_prepend2 (CRPropList * a_this,
+ CRString * a_prop_name, CRDeclaration * a_decl)
+{
+ CRPropList *list = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prop_name && a_decl, NULL);
+
+ list = cr_prop_list_allocate ();
+ g_return_val_if_fail (list, NULL);
+ PRIVATE (list)->prop = a_prop_name;
+ PRIVATE (list)->decl = a_decl;
+ result = cr_prop_list_prepend (a_this, list);
+ return result;
+}
+
+/**
+ * cr_prop_list_set_prop:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: the property to set
+ *
+ *Sets the property of a CRPropList
+ */
+enum CRStatus
+cr_prop_list_set_prop (CRPropList * a_this, CRString * a_prop)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prop, CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->prop = a_prop;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_get_prop:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: out parameter. The returned property
+ *
+ *Getter of the property associated to the current instance
+ *of #CRPropList
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_prop_list_get_prop (CRPropList const * a_this, CRString ** a_prop)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prop, CR_BAD_PARAM_ERROR);
+
+ *a_prop = PRIVATE (a_this)->prop;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_set_decl:
+ * @a_this: the current instance of #CRPropList
+ * @a_decl: the new property value.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_prop_list_set_decl (CRPropList * a_this, CRDeclaration * a_decl)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_decl, CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->decl = a_decl;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_get_decl:
+ * @a_this: the current instance of #CRPropList
+ * @a_decl: out parameter. The property value
+ *
+ * Returns CR_OK upon successful completion.
+ */
+enum CRStatus
+cr_prop_list_get_decl (CRPropList const * a_this, CRDeclaration ** a_decl)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_decl, CR_BAD_PARAM_ERROR);
+
+ *a_decl = PRIVATE (a_this)->decl;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_lookup_prop:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: the property to lookup
+ *@a_prop_list: out parameter. The property/declaration
+ *pair found (if and only if the function returned code if CR_OK)
+ *
+ *Lookup a given property/declaration pair
+ *
+ *Returns CR_OK if a prop/decl pair has been found,
+ *CR_VALUE_NOT_FOUND_ERROR if not, or an error code if something
+ *bad happens.
+ */
+enum CRStatus
+cr_prop_list_lookup_prop (CRPropList * a_this,
+ CRString * a_prop, CRPropList ** a_pair)
+{
+ CRPropList *cur = NULL;
+
+ g_return_val_if_fail (a_prop && a_pair, CR_BAD_PARAM_ERROR);
+
+ if (!a_this)
+ return CR_VALUE_NOT_FOUND_ERROR;
+
+ g_return_val_if_fail (PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ for (cur = a_this; cur; cur = PRIVATE (cur)->next) {
+ if (PRIVATE (cur)->prop
+ && PRIVATE (cur)->prop->stryng
+ && PRIVATE (cur)->prop->stryng->str
+ && a_prop->stryng
+ && a_prop->stryng->str
+ && !strcmp (PRIVATE (cur)->prop->stryng->str,
+ a_prop->stryng->str))
+ break;
+ }
+
+ if (cur) {
+ *a_pair = cur;
+ return CR_OK;
+ }
+
+ return CR_VALUE_NOT_FOUND_ERROR;
+}
+
+/**
+ * cr_prop_list_get_next:
+ *@a_this: the current instance of CRPropList
+ *
+ *Gets the next prop/decl pair in the list
+ *
+ *Returns the next prop/declaration pair of the list,
+ *or NULL if we reached end of list (or if an error occurs)
+ */
+CRPropList *
+cr_prop_list_get_next (CRPropList * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), NULL);
+
+ return PRIVATE (a_this)->next;
+}
+
+/**
+ * cr_prop_list_get_prev:
+ *@a_this: the current instance of CRPropList
+ *
+ *Gets the previous prop/decl pair in the list
+ *
+ *Returns the previous prop/declaration pair of the list,
+ *or NULL if we reached end of list (or if an error occurs)
+ */
+CRPropList *
+cr_prop_list_get_prev (CRPropList * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), NULL);
+
+ return PRIVATE (a_this)->prev;
+}
+
+/**
+ * cr_prop_list_unlink:
+ *@a_this: the current list of prop/decl pairs
+ *@a_pair: the prop/decl pair to unlink.
+ *
+ *Unlinks a prop/decl pair from the list
+ *
+ *Returns the new list or NULL in case of an error.
+ */
+CRPropList *
+cr_prop_list_unlink (CRPropList * a_this, CRPropList * a_pair)
+{
+ CRPropList *prev = NULL,
+ *next = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pair, NULL);
+
+ /*some sanity checks */
+ if (PRIVATE (a_pair)->next) {
+ next = PRIVATE (a_pair)->next;
+ g_return_val_if_fail (PRIVATE (next), NULL);
+ g_return_val_if_fail (PRIVATE (next)->prev == a_pair, NULL);
+ }
+ if (PRIVATE (a_pair)->prev) {
+ prev = PRIVATE (a_pair)->prev;
+ g_return_val_if_fail (PRIVATE (prev), NULL);
+ g_return_val_if_fail (PRIVATE (prev)->next == a_pair, NULL);
+ }
+ if (prev) {
+ PRIVATE (prev)->next = next;
+ }
+ if (next) {
+ PRIVATE (next)->prev = prev;
+ }
+ PRIVATE (a_pair)->prev = PRIVATE (a_pair)->next = NULL;
+ if (a_this == a_pair) {
+ if (next)
+ return next;
+ return NULL;
+ }
+ return a_this;
+}
+
+/**
+ * cr_prop_list_destroy:
+ * @a_this: the current instance of #CRPropList
+ */
+void
+cr_prop_list_destroy (CRPropList * a_this)
+{
+ CRPropList *tail = NULL,
+ *cur = NULL;
+
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ for (tail = a_this;
+ tail && PRIVATE (tail) && PRIVATE (tail)->next;
+ tail = cr_prop_list_get_next (tail)) ;
+ g_return_if_fail (tail);
+
+ cur = tail;
+
+ while (cur) {
+ tail = PRIVATE (cur)->prev;
+ if (tail && PRIVATE (tail))
+ PRIVATE (tail)->next = NULL;
+ PRIVATE (cur)->prev = NULL;
+ g_free (PRIVATE (cur));
+ PRIVATE (cur) = NULL;
+ g_free (cur);
+ cur = tail;
+ }
+}
diff --git a/src/st/croco/cr-prop-list.h b/src/st/croco/cr-prop-list.h
new file mode 100644
index 0000000..797ba43
--- /dev/null
+++ b/src/st/croco/cr-prop-list.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#ifndef __CR_PROP_LIST_H__
+#define __CR_PROP_LIST_H__
+
+#include "cr-utils.h"
+#include "cr-declaration.h"
+#include "cr-string.h"
+
+G_BEGIN_DECLS
+
+typedef struct _CRPropList CRPropList ;
+typedef struct _CRPropListPriv CRPropListPriv ;
+
+struct _CRPropList
+{
+ CRPropListPriv * priv;
+} ;
+
+CRPropList * cr_prop_list_append (CRPropList *a_this,
+ CRPropList *a_to_append) ;
+
+CRPropList * cr_prop_list_append2 (CRPropList *a_this,
+ CRString *a_prop,
+ CRDeclaration *a_decl) ;
+
+CRPropList * cr_prop_list_prepend (CRPropList *a_this,
+ CRPropList *a_to_append) ;
+
+CRPropList * cr_prop_list_prepend2 (CRPropList *a_this,
+ CRString *a_prop,
+ CRDeclaration *a_decl) ;
+
+enum CRStatus cr_prop_list_set_prop (CRPropList *a_this,
+ CRString *a_prop) ;
+
+enum CRStatus cr_prop_list_get_prop (CRPropList const *a_this,
+ CRString **a_prop) ;
+
+enum CRStatus cr_prop_list_lookup_prop (CRPropList *a_this,
+ CRString *a_prop,
+ CRPropList**a_pair) ;
+
+CRPropList * cr_prop_list_get_next (CRPropList *a_this) ;
+
+CRPropList * cr_prop_list_get_prev (CRPropList *a_this) ;
+
+enum CRStatus cr_prop_list_set_decl (CRPropList *a_this,
+ CRDeclaration *a_decl);
+
+enum CRStatus cr_prop_list_get_decl (CRPropList const *a_this,
+ CRDeclaration **a_decl) ;
+
+CRPropList * cr_prop_list_unlink (CRPropList *a_this,
+ CRPropList *a_pair) ;
+
+void cr_prop_list_destroy (CRPropList *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_PROP_LIST_H__*/
diff --git a/src/st/croco/cr-pseudo.c b/src/st/croco/cr-pseudo.c
new file mode 100644
index 0000000..f81f9a6
--- /dev/null
+++ b/src/st/croco/cr-pseudo.c
@@ -0,0 +1,166 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include "cr-pseudo.h"
+
+/**
+ *@CRPseudo:
+ *The definition of the #CRPseudo class.
+ */
+
+/**
+ * cr_pseudo_new:
+ *Constructor of the #CRPseudo class.
+ *
+ *Returns the newly build instance.
+ */
+CRPseudo *
+cr_pseudo_new (void)
+{
+ CRPseudo *result = NULL;
+
+ result = g_malloc0 (sizeof (CRPseudo));
+
+ return result;
+}
+
+/**
+ * cr_pseudo_to_string:
+ * @a_this: the current instance of #CRPseud.
+ *
+ * Returns the serialized pseudo. Caller must free the returned
+ * string using g_free().
+ */
+guchar *
+cr_pseudo_to_string (CRPseudo const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+
+ if (a_this->type == IDENT_PSEUDO) {
+ guchar *name = NULL;
+
+ if (a_this->name == NULL) {
+ goto error;
+ }
+
+ name = (guchar *) g_strndup (a_this->name->stryng->str,
+ a_this->name->stryng->len);
+
+ if (name) {
+ g_string_append (str_buf, (const gchar *) name);
+ g_free (name);
+ name = NULL;
+ }
+ } else if (a_this->type == FUNCTION_PSEUDO) {
+ guchar *name = NULL,
+ *arg = NULL;
+
+ if (a_this->name == NULL)
+ goto error;
+
+ name = (guchar *) g_strndup (a_this->name->stryng->str,
+ a_this->name->stryng->len);
+
+ if (a_this->extra) {
+ arg = (guchar *) g_strndup (a_this->extra->stryng->str,
+ a_this->extra->stryng->len);
+ }
+
+ if (name) {
+ g_string_append_printf (str_buf, "%s(", name);
+ g_free (name);
+ name = NULL;
+
+ if (arg) {
+ g_string_append (str_buf, (const gchar *) arg);
+ g_free (arg);
+ arg = NULL;
+ }
+
+ g_string_append_c (str_buf, ')');
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+
+ error:
+ g_string_free (str_buf, TRUE);
+ return NULL;
+}
+
+/**
+ * cr_pseudo_dump:
+ *@a_this: the current instance of pseudo
+ *@a_fp: the destination file pointer.
+ *
+ *Dumps the pseudo to a file.
+ *
+ */
+void
+cr_pseudo_dump (CRPseudo const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ if (a_this) {
+ tmp_str = cr_pseudo_to_string (a_this);
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+}
+
+/**
+ * cr_pseudo_destroy:
+ *@a_this: the current instance to destroy.
+ *
+ *destructor of the #CRPseudo class.
+ */
+void
+cr_pseudo_destroy (CRPseudo * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->name) {
+ cr_string_destroy (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (a_this->extra) {
+ cr_string_destroy (a_this->extra);
+ a_this->extra = NULL;
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-pseudo.h b/src/st/croco/cr-pseudo.h
new file mode 100644
index 0000000..8917da4
--- /dev/null
+++ b/src/st/croco/cr-pseudo.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyright information
+ */
+
+#ifndef __CR_PSEUDO_H__
+#define __CR_PSEUDO_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-attr-sel.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+enum CRPseudoType
+{
+ IDENT_PSEUDO = 0,
+ FUNCTION_PSEUDO
+} ;
+
+typedef struct _CRPseudo CRPseudo ;
+
+/**
+ *The CRPseudo Class.
+ *Abstract a "pseudo" as defined by the css2 spec
+ *in appendix D.1 .
+ */
+struct _CRPseudo
+{
+ enum CRPseudoType type ;
+ CRString *name ;
+ CRString *extra ;
+ CRParsingLocation location ;
+} ;
+
+CRPseudo * cr_pseudo_new (void) ;
+
+guchar * cr_pseudo_to_string (CRPseudo const *a_this) ;
+
+void cr_pseudo_dump (CRPseudo const *a_this, FILE *a_fp) ;
+
+void cr_pseudo_destroy (CRPseudo *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_PSEUDO_H__*/
diff --git a/src/st/croco/cr-rgb.c b/src/st/croco/cr-rgb.c
new file mode 100644
index 0000000..a2b478f
--- /dev/null
+++ b/src/st/croco/cr-rgb.c
@@ -0,0 +1,604 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "cr-rgb.h"
+#include "cr-term.h"
+#include "cr-parser.h"
+
+static const CRRgb gv_standard_colors[] = {
+ {(const guchar*)"aliceblue", 240, 248, 255, FALSE, {0,0,0}},
+ {(const guchar*)"antiquewhite", 250, 235, 215, FALSE, {0,0,0}},
+ {(const guchar*)"aqua", 0, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"aquamarine", 127, 255, 212, FALSE, {0,0,0}},
+ {(const guchar*)"azure", 240, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"beige", 245, 245, 220, FALSE, {0,0,0}},
+ {(const guchar*)"bisque", 255, 228, 196, FALSE, {0,0,0}},
+ {(const guchar*)"black", 0, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"blanchedalmond", 255, 235, 205, FALSE, {0,0,0}},
+ {(const guchar*)"blue", 0, 0, 255, FALSE, {0,0,0}},
+ {(const guchar*)"blueviolet", 138, 43, 226, FALSE, {0,0,0}},
+ {(const guchar*)"brown", 165, 42, 42, FALSE, {0,0,0}},
+ {(const guchar*)"burlywood", 222, 184, 135, FALSE, {0,0,0}},
+ {(const guchar*)"cadetblue", 95, 158, 160, FALSE, {0,0,0}},
+ {(const guchar*)"chartreuse", 127, 255, 0, FALSE, {0,0,0}},
+ {(const guchar*)"chocolate", 210, 105, 30, FALSE, {0,0,0}},
+ {(const guchar*)"coral", 255, 127, 80, FALSE, {0,0,0}},
+ {(const guchar*)"cornflowerblue", 100, 149, 237, FALSE, {0,0,0}},
+ {(const guchar*)"cornsilk", 255, 248, 220, FALSE, {0,0,0}},
+ {(const guchar*)"crimson", 220, 20, 60, FALSE, {0,0,0}},
+ {(const guchar*)"cyan", 0, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"darkblue", 0, 0, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkcyan", 0, 139, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkgoldenrod", 184, 134, 11, FALSE, {0,0,0}},
+ {(const guchar*)"darkgray", 169, 169, 169, FALSE, {0,0,0}},
+ {(const guchar*)"darkgreen", 0, 100, 0, FALSE, {0,0,0}},
+ {(const guchar*)"darkgrey", 169, 169, 169, FALSE, {0,0,0}},
+ {(const guchar*)"darkkhaki", 189, 183, 107, FALSE, {0,0,0}},
+ {(const guchar*)"darkmagenta", 139, 0, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkolivegreen", 85, 107, 47, FALSE, {0,0,0}},
+ {(const guchar*)"darkorange", 255, 140, 0, FALSE, {0,0,0}},
+ {(const guchar*)"darkorchid", 153, 50, 204, FALSE, {0,0,0}},
+ {(const guchar*)"darkred", 139, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"darksalmon", 233, 150, 122, FALSE, {0,0,0}},
+ {(const guchar*)"darkseagreen", 143, 188, 143, FALSE, {0,0,0}},
+ {(const guchar*)"darkslateblue", 72, 61, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkslategray", 47, 79, 79, FALSE, {0,0,0}},
+ {(const guchar*)"darkslategrey", 47, 79, 79, FALSE, {0,0,0}},
+ {(const guchar*)"darkturquoise", 0, 206, 209, FALSE, {0,0,0}},
+ {(const guchar*)"darkviolet", 148, 0, 211, FALSE, {0,0,0}},
+ {(const guchar*)"deeppink", 255, 20, 147, FALSE, {0,0,0}},
+ {(const guchar*)"deepskyblue", 0, 191, 255, FALSE, {0,0,0}},
+ {(const guchar*)"dimgray", 105, 105, 105, FALSE, {0,0,0}},
+ {(const guchar*)"dimgrey", 105, 105, 105, FALSE, {0,0,0}},
+ {(const guchar*)"dodgerblue", 30, 144, 255, FALSE, {0,0,0}},
+ {(const guchar*)"firebrick", 178, 34, 34, FALSE, {0,0,0}},
+ {(const guchar*)"floralwhite", 255, 250, 240, FALSE, {0,0,0}},
+ {(const guchar*)"forestgreen", 34, 139, 34, FALSE, {0,0,0}},
+ {(const guchar*)"fuchsia", 255, 0, 255, FALSE, {0,0,0}},
+ {(const guchar*)"gainsboro", 220, 220, 220, FALSE, {0,0,0}},
+ {(const guchar*)"ghostwhite", 248, 248, 255, FALSE, {0,0,0}},
+ {(const guchar*)"gold", 255, 215, 0, FALSE, {0,0,0}},
+ {(const guchar*)"goldenrod", 218, 165, 32, FALSE, {0,0,0}},
+ {(const guchar*)"gray", 128, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"green", 0, 128, 0, FALSE, {0,0,0}},
+ {(const guchar*)"greenyellow", 173, 255, 47, FALSE, {0,0,0}},
+ {(const guchar*)"grey", 128, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"honeydew", 240, 255, 240, FALSE, {0,0,0}},
+ {(const guchar*)"hotpink", 255, 105, 180, FALSE, {0,0,0}},
+ {(const guchar*)"indianred", 205, 92, 92, FALSE, {0,0,0}},
+ {(const guchar*)"indigo", 75, 0, 130, FALSE, {0,0,0}},
+ {(const guchar*)"ivory", 255, 255, 240, FALSE, {0,0,0}},
+ {(const guchar*)"khaki", 240, 230, 140, FALSE, {0,0,0}},
+ {(const guchar*)"lavender", 230, 230, 250, FALSE, {0,0,0}},
+ {(const guchar*)"lavenderblush", 255, 240, 245, FALSE, {0,0,0}},
+ {(const guchar*)"lawngreen", 124, 252, 0, FALSE, {0,0,0}},
+ {(const guchar*)"lemonchiffon", 255, 250, 205, FALSE, {0,0,0}},
+ {(const guchar*)"lightblue", 173, 216, 230, FALSE, {0,0,0}},
+ {(const guchar*)"lightcoral", 240, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"lightcyan", 224, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"lightgoldenrodyellow", 250, 250, 210, FALSE, {0,0,0}},
+ {(const guchar*)"lightgray", 211, 211, 211, FALSE, {0,0,0}},
+ {(const guchar*)"lightgreen", 144, 238, 144, FALSE, {0,0,0}},
+ {(const guchar*)"lightgrey", 211, 211, 211, FALSE, {0,0,0}},
+ {(const guchar*)"lightpink", 255, 182, 193, FALSE, {0,0,0}},
+ {(const guchar*)"lightsalmon", 255, 160, 122, FALSE, {0,0,0}},
+ {(const guchar*)"lightseagreen", 32, 178, 170, FALSE, {0,0,0}},
+ {(const guchar*)"lightskyblue", 135, 206, 250, FALSE, {0,0,0}},
+ {(const guchar*)"lightslategray", 119, 136, 153, FALSE, {0,0,0}},
+ {(const guchar*)"lightslategrey", 119, 136, 153, FALSE, {0,0,0}},
+ {(const guchar*)"lightsteelblue", 176, 196, 222, FALSE, {0,0,0}},
+ {(const guchar*)"lightyellow", 255, 255, 224, FALSE, {0,0,0}},
+ {(const guchar*)"lime", 0, 255, 0, FALSE, {0,0,0}},
+ {(const guchar*)"limegreen", 50, 205, 50, FALSE, {0,0,0}},
+ {(const guchar*)"linen", 250, 240, 230, FALSE, {0,0,0}},
+ {(const guchar*)"magenta", 255, 0, 255, FALSE, {0,0,0}},
+ {(const guchar*)"maroon", 128, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"mediumaquamarine", 102, 205, 170, FALSE, {0,0,0}},
+ {(const guchar*)"mediumblue", 0, 0, 205, FALSE, {0,0,0}},
+ {(const guchar*)"mediumorchid", 186, 85, 211, FALSE, {0,0,0}},
+ {(const guchar*)"mediumpurple", 147, 112, 219, FALSE, {0,0,0}},
+ {(const guchar*)"mediumseagreen", 60, 179, 113, FALSE, {0,0,0}},
+ {(const guchar*)"mediumslateblue", 123, 104, 238, FALSE, {0,0,0}},
+ {(const guchar*)"mediumspringgreen", 0, 250, 154, FALSE, {0,0,0}},
+ {(const guchar*)"mediumturquoise", 72, 209, 204, FALSE, {0,0,0}},
+ {(const guchar*)"mediumvioletred", 199, 21, 133, FALSE, {0,0,0}},
+ {(const guchar*)"midnightblue", 25, 25, 112, FALSE, {0,0,0}},
+ {(const guchar*)"mintcream", 245, 255, 250, FALSE, {0,0,0}},
+ {(const guchar*)"mistyrose", 255, 228, 225, FALSE, {0,0,0}},
+ {(const guchar*)"moccasin", 255, 228, 181, FALSE, {0,0,0}},
+ {(const guchar*)"navajowhite", 255, 222, 173, FALSE, {0,0,0}},
+ {(const guchar*)"navy", 0, 0, 128, FALSE, {0,0,0}},
+ {(const guchar*)"oldlace", 253, 245, 230, FALSE, {0,0,0}},
+ {(const guchar*)"olive", 128, 128, 0, FALSE, {0,0,0}},
+ {(const guchar*)"olivedrab", 107, 142, 35, FALSE, {0,0,0}},
+ {(const guchar*)"orange", 255, 165, 0, FALSE, {0,0,0}},
+ {(const guchar*)"orangered", 255, 69, 0, FALSE, {0,0,0}},
+ {(const guchar*)"orchid", 218, 112, 214, FALSE, {0,0,0}},
+ {(const guchar*)"palegoldenrod", 238, 232, 170, FALSE, {0,0,0}},
+ {(const guchar*)"palegreen", 152, 251, 152, FALSE, {0,0,0}},
+ {(const guchar*)"paleturquoise", 175, 238, 238, FALSE, {0,0,0}},
+ {(const guchar*)"palevioletred", 219, 112, 147, FALSE, {0,0,0}},
+ {(const guchar*)"papayawhip", 255, 239, 213, FALSE, {0,0,0}},
+ {(const guchar*)"peachpuff", 255, 218, 185, FALSE, {0,0,0}},
+ {(const guchar*)"peru", 205, 133, 63, FALSE, {0,0,0}},
+ {(const guchar*)"pink", 255, 192, 203, FALSE, {0,0,0}},
+ {(const guchar*)"plum", 221, 160, 221, FALSE, {0,0,0}},
+ {(const guchar*)"powderblue", 176, 224, 230, FALSE, {0,0,0}},
+ {(const guchar*)"purple", 128, 0, 128, FALSE, {0,0,0}},
+ {(const guchar*)"red", 255, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"rosybrown", 188, 143, 143, FALSE, {0,0,0}},
+ {(const guchar*)"royalblue", 65, 105, 225, FALSE, {0,0,0}},
+ {(const guchar*)"saddlebrown", 139, 69, 19, FALSE, {0,0,0}},
+ {(const guchar*)"salmon", 250, 128, 114, FALSE, {0,0,0}},
+ {(const guchar*)"sandybrown", 244, 164, 96, FALSE, {0,0,0}},
+ {(const guchar*)"seagreen", 46, 139, 87, FALSE, {0,0,0}},
+ {(const guchar*)"seashell", 255, 245, 238, FALSE, {0,0,0}},
+ {(const guchar*)"sienna", 160, 82, 45, FALSE, {0,0,0}},
+ {(const guchar*)"silver", 192, 192, 192, FALSE, {0,0,0}},
+ {(const guchar*)"skyblue", 135, 206, 235, FALSE, {0,0,0}},
+ {(const guchar*)"slateblue", 106, 90, 205, FALSE, {0,0,0}},
+ {(const guchar*)"slategray", 112, 128, 144, FALSE, {0,0,0}},
+ {(const guchar*)"slategrey", 112, 128, 144, FALSE, {0,0,0}},
+ {(const guchar*)"snow", 255, 250, 250, FALSE, {0,0,0}},
+ {(const guchar*)"springgreen", 0, 255, 127, FALSE, {0,0,0}},
+ {(const guchar*)"steelblue", 70, 130, 180, FALSE, {0,0,0}},
+ {(const guchar*)"tan", 210, 180, 140, FALSE, {0,0,0}},
+ {(const guchar*)"teal", 0, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"thistle", 216, 191, 216, FALSE, {0,0,0}},
+ {(const guchar*)"tomato", 255, 99, 71, FALSE, {0,0,0}},
+ {(const guchar*)"turquoise", 64, 224, 208, FALSE, {0,0,0}},
+ {(const guchar*)"violet", 238, 130, 238, FALSE, {0,0,0}},
+ {(const guchar*)"wheat", 245, 222, 179, FALSE, {0,0,0}},
+ {(const guchar*)"white", 255, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"whitesmoke", 245, 245, 245, FALSE, {0,0,0}},
+ {(const guchar*)"yellow", 255, 255, 0, FALSE, {0,0,0}},
+ {(const guchar*)"yellowgreen", 154, 205, 50, FALSE, {0,0,0}}
+};
+
+/**
+ * cr_rgb_new:
+ *
+ *The default constructor of #CRRgb.
+ *
+ *Returns the newly built instance of #CRRgb
+ */
+CRRgb *
+cr_rgb_new (void)
+{
+ CRRgb *result = NULL;
+
+ result = g_try_malloc (sizeof (CRRgb));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("No more memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRRgb));
+
+ return result;
+}
+
+/**
+ * cr_rgb_new_with_vals:
+ *@a_red: the red component of the color.
+ *@a_green: the green component of the color.
+ *@a_blue: the blue component of the color.
+ *@a_unit: the unit of the rgb values.
+ *(either percentage or integer values)
+ *
+ *A constructor of #CRRgb.
+ *
+ *Returns the newly built instance of #CRRgb.
+ */
+CRRgb *
+cr_rgb_new_with_vals (gulong a_red, gulong a_green,
+ gulong a_blue, gboolean a_is_percentage)
+{
+ CRRgb *result = NULL;
+
+ result = cr_rgb_new ();
+
+ g_return_val_if_fail (result, NULL);
+
+ result->red = a_red;
+ result->green = a_green;
+ result->blue = a_blue;
+ result->is_percentage = a_is_percentage;
+
+ return result;
+}
+
+/**
+ * cr_rgb_to_string:
+ *@a_this: the instance of #CRRgb to serialize.
+ *
+ *Serializes the rgb into a zero terminated string.
+ *
+ *Returns the zero terminated string containing the serialized
+ *rgb. MUST BE FREED by the caller using g_free().
+ */
+guchar *
+cr_rgb_to_string (CRRgb const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ if (a_this->is_percentage == 1) {
+ g_string_append_printf (str_buf, "%ld", a_this->red);
+
+ g_string_append (str_buf, "%, ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->green);
+ g_string_append (str_buf, "%, ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->blue);
+ g_string_append_c (str_buf, '%');
+ } else {
+ g_string_append_printf (str_buf, "%ld", a_this->red);
+ g_string_append (str_buf, ", ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->green);
+ g_string_append (str_buf, ", ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->blue);
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_rgb_dump:
+ *@a_this: the "this pointer" of
+ *the current instance of #CRRgb.
+ *@a_fp: the destination file pointer.
+ *
+ *Dumps the current instance of #CRRgb
+ *to a file.
+ */
+void
+cr_rgb_dump (CRRgb const * a_this, FILE * a_fp)
+{
+ guchar *str = NULL;
+
+ g_return_if_fail (a_this);
+
+ str = cr_rgb_to_string (a_this);
+
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+}
+
+/**
+ * cr_rgb_compute_from_percentage:
+ *@a_this: the current instance of #CRRgb
+ *
+ *If the rgb values are expressed in percentage,
+ *compute their real value.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_compute_from_percentage (CRRgb * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ if (a_this->is_percentage == FALSE)
+ return CR_OK;
+ a_this->red = a_this->red * 255 / 100;
+ a_this->green = a_this->green * 255 / 100;
+ a_this->blue = a_this->blue * 255 / 100;
+ a_this->is_percentage = FALSE;
+ return CR_OK;
+}
+
+/**
+ * cr_rgb_set:
+ *@a_this: the current instance of #CRRgb.
+ *@a_red: the red value.
+ *@a_green: the green value.
+ *@a_blue: the blue value.
+ *
+ *Sets rgb values to the RGB.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_rgb_set (CRRgb * a_this, gulong a_red,
+ gulong a_green, gulong a_blue, gboolean a_is_percentage)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ if (a_is_percentage != FALSE) {
+ g_return_val_if_fail (a_red <= 100
+ && a_green <= 100
+ && a_blue <= 100, CR_BAD_PARAM_ERROR);
+ }
+
+ a_this->is_percentage = a_is_percentage;
+
+ a_this->red = a_red;
+ a_this->green = a_green;
+ a_this->blue = a_blue;
+ return CR_OK;
+}
+
+/**
+ * cr_rgb_set_from_rgb:
+ *@a_this: the current instance of #CRRgb.
+ *@a_rgb: the rgb to "copy"
+ *
+ *Sets the rgb from an other one.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_set_from_rgb (CRRgb * a_this, CRRgb const * a_rgb)
+{
+ g_return_val_if_fail (a_this && a_rgb, CR_BAD_PARAM_ERROR);
+
+ cr_rgb_copy (a_this, a_rgb) ;
+
+ return CR_OK;
+}
+
+static int
+cr_rgb_color_name_compare (const void *a,
+ const void *b)
+{
+ const char *a_color_name = a;
+ const CRRgb *rgb = b;
+
+ return g_ascii_strcasecmp (a_color_name, (const char *) rgb->name);
+}
+
+/**
+ * cr_rgb_set_from_name:
+ * @a_this: the current instance of #CRRgb
+ * @a_color_name: the color name
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_set_from_name (CRRgb * a_this, const guchar * a_color_name)
+{
+ enum CRStatus status = CR_OK;
+ CRRgb *result;
+
+ g_return_val_if_fail (a_this && a_color_name, CR_BAD_PARAM_ERROR);
+
+ result = bsearch (a_color_name,
+ gv_standard_colors,
+ G_N_ELEMENTS (gv_standard_colors),
+ sizeof (gv_standard_colors[0]),
+ cr_rgb_color_name_compare);
+ if (result != NULL)
+ cr_rgb_set_from_rgb (a_this, result);
+ else
+ status = CR_UNKNOWN_TYPE_ERROR;
+
+ return status;
+}
+
+/**
+ * cr_rgb_set_from_hex_str:
+ * @a_this: the current instance of #CRRgb
+ * @a_hex: the hexadecimal value to set.
+ *
+ * Returns CR_OK upon successful completion.
+ */
+enum CRStatus
+cr_rgb_set_from_hex_str (CRRgb * a_this, const guchar * a_hex)
+{
+ enum CRStatus status = CR_OK;
+ gulong i = 0;
+ guchar colors[3] = { 0 };
+
+ g_return_val_if_fail (a_this && a_hex, CR_BAD_PARAM_ERROR);
+
+ if (strlen ((const char *) a_hex) == 3) {
+ for (i = 0; i < 3; i++) {
+ if (a_hex[i] >= '0' && a_hex[i] <= '9') {
+ colors[i] = a_hex[i] - '0';
+ colors[i] = (colors[i] << 4) | colors[i];
+ } else if (a_hex[i] >= 'a' && a_hex[i] <= 'z') {
+ colors[i] = 10 + a_hex[i] - 'a';
+ colors[i] = (colors[i] << 4) | colors[i];
+ } else if (a_hex[i] >= 'A' && a_hex[i] <= 'Z') {
+ colors[i] = 10 + a_hex[i] - 'A';
+ colors[i] = (colors[i] << 4) | colors[i];
+ } else {
+ status = CR_UNKNOWN_TYPE_ERROR;
+ }
+ }
+ } else if (strlen ((const char *) a_hex) == 6) {
+ for (i = 0; i < 6; i++) {
+ if (a_hex[i] >= '0' && a_hex[i] <= '9') {
+ colors[i / 2] <<= 4;
+ colors[i / 2] |= a_hex[i] - '0';
+ status = CR_OK;
+ } else if (a_hex[i] >= 'a' && a_hex[i] <= 'z') {
+ colors[i / 2] <<= 4;
+ colors[i / 2] |= 10 + a_hex[i] - 'a';
+ status = CR_OK;
+ } else if (a_hex[i] >= 'A' && a_hex[i] <= 'Z') {
+ colors[i / 2] <<= 4;
+ colors[i / 2] |= 10 + a_hex[i] - 'A';
+ status = CR_OK;
+ } else {
+ status = CR_UNKNOWN_TYPE_ERROR;
+ }
+ }
+ } else {
+ status = CR_UNKNOWN_TYPE_ERROR;
+ }
+
+ if (status == CR_OK) {
+ status = cr_rgb_set (a_this, colors[0],
+ colors[1], colors[2], FALSE);
+ }
+ return status;
+}
+
+/**
+ * cr_rgb_set_from_term:
+ *@a_this: the instance of #CRRgb to set
+ *@a_value: the terminal from which to set
+ *
+ *Set the rgb from a terminal symbol
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_set_from_term (CRRgb *a_this, const struct _CRTerm *a_value)
+{
+ enum CRStatus status = CR_OK ;
+ g_return_val_if_fail (a_this && a_value,
+ CR_BAD_PARAM_ERROR) ;
+
+ switch(a_value->type) {
+ case TERM_RGB:
+ if (a_value->content.rgb) {
+ cr_rgb_set_from_rgb
+ (a_this, a_value->content.rgb) ;
+ }
+ break ;
+ case TERM_IDENT:
+ if (a_value->content.str
+ && a_value->content.str->stryng
+ && a_value->content.str->stryng->str) {
+ status = cr_rgb_set_from_name
+ (a_this,
+ (const guchar *) a_value->content.str->stryng->str) ;
+ } else {
+ cr_utils_trace_info
+ ("a_value has NULL string value") ;
+ }
+ break ;
+ case TERM_HASH:
+ if (a_value->content.str
+ && a_value->content.str->stryng
+ && a_value->content.str->stryng->str) {
+ status = cr_rgb_set_from_hex_str
+ (a_this,
+ (const guchar *) a_value->content.str->stryng->str) ;
+ } else {
+ cr_utils_trace_info
+ ("a_value has NULL string value") ;
+ }
+ break ;
+ default:
+ status = CR_UNKNOWN_TYPE_ERROR ;
+ }
+ return status ;
+}
+
+enum CRStatus
+cr_rgb_copy (CRRgb *a_dest, CRRgb const *a_src)
+{
+ g_return_val_if_fail (a_dest && a_src,
+ CR_BAD_PARAM_ERROR) ;
+
+ memcpy (a_dest, a_src, sizeof (CRRgb)) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_rgb_destroy:
+ *@a_this: the "this pointer" of the
+ *current instance of #CRRgb.
+ *
+ *Destructor of #CRRgb.
+ */
+void
+cr_rgb_destroy (CRRgb * a_this)
+{
+ g_return_if_fail (a_this);
+ g_free (a_this);
+}
+
+/**
+ * cr_rgb_parse_from_buf:
+ *@a_str: a string that contains a color description
+ *@a_enc: the encoding of a_str
+ *
+ *Parses a text buffer that contains a rgb color
+ *
+ *Returns the parsed color, or NULL in case of error
+ */
+CRRgb *
+cr_rgb_parse_from_buf (const guchar *a_str,
+ enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_OK ;
+ CRTerm *value = NULL ;
+ CRParser * parser = NULL;
+ CRRgb *result = NULL;
+
+ g_return_val_if_fail (a_str, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar *) a_str, strlen ((const char *) a_str), a_enc, FALSE);
+
+ g_return_val_if_fail (parser, NULL);
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser) ;
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_term (parser, &value);
+ if (status != CR_OK)
+ goto cleanup;
+
+ result = cr_rgb_new ();
+ if (!result)
+ goto cleanup;
+
+ status = cr_rgb_set_from_term (result, value);
+
+cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+ if (value) {
+ cr_term_destroy(value);
+ value = NULL;
+ }
+ return result ;
+}
+
diff --git a/src/st/croco/cr-rgb.h b/src/st/croco/cr-rgb.h
new file mode 100644
index 0000000..a77e309
--- /dev/null
+++ b/src/st/croco/cr-rgb.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * see COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_RGB_H__
+#define __CR_RGB_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRRgb CRRgb ;
+struct _CRRgb
+{
+ /*
+ *the unit of the rgb.
+ *Either NO_UNIT (integer) or
+ *UNIT_PERCENTAGE (percentage).
+ */
+ const guchar *name ;
+ glong red ;
+ glong green ;
+ glong blue ;
+ gboolean is_percentage ;
+ CRParsingLocation location ;
+} ;
+
+CRRgb * cr_rgb_new (void) ;
+
+CRRgb * cr_rgb_new_with_vals (gulong a_red, gulong a_green,
+ gulong a_blue, gboolean a_is_percentage) ;
+
+CRRgb *cr_rgb_parse_from_buf(const guchar *a_str,
+ enum CREncoding a_enc);
+
+enum CRStatus cr_rgb_compute_from_percentage (CRRgb *a_this) ;
+
+enum CRStatus cr_rgb_set (CRRgb *a_this, gulong a_red,
+ gulong a_green, gulong a_blue,
+ gboolean a_is_percentage) ;
+
+enum CRStatus cr_rgb_copy (CRRgb *a_dest, CRRgb const *a_src) ;
+
+enum CRStatus cr_rgb_set_from_rgb (CRRgb *a_this, CRRgb const *a_rgb) ;
+
+enum CRStatus cr_rgb_set_from_name (CRRgb *a_this, const guchar *a_color_name) ;
+
+enum CRStatus cr_rgb_set_from_hex_str (CRRgb *a_this, const guchar * a_hex_value) ;
+
+struct _CRTerm;
+
+enum CRStatus cr_rgb_set_from_term (CRRgb *a_this, const struct _CRTerm *a_value);
+
+guchar * cr_rgb_to_string (CRRgb const *a_this) ;
+
+void cr_rgb_dump (CRRgb const *a_this, FILE *a_fp) ;
+
+void cr_rgb_destroy (CRRgb *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_RGB_H__*/
diff --git a/src/st/croco/cr-selector.c b/src/st/croco/cr-selector.c
new file mode 100644
index 0000000..c9aad43
--- /dev/null
+++ b/src/st/croco/cr-selector.c
@@ -0,0 +1,305 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-selector.h"
+#include "cr-parser.h"
+
+/**
+ * cr_selector_new:
+ *
+ *@a_simple_sel: the initial simple selector list
+ *of the current instance of #CRSelector.
+ *
+ *Creates a new instance of #CRSelector.
+ *
+ *Returns the newly built instance of #CRSelector, or
+ *NULL in case of failure.
+ */
+CRSelector *
+cr_selector_new (CRSimpleSel * a_simple_sel)
+{
+ CRSelector *result = NULL;
+
+ result = g_try_malloc (sizeof (CRSelector));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRSelector));
+ result->simple_sel = a_simple_sel;
+ return result;
+}
+
+CRSelector *
+cr_selector_parse_from_buf (const guchar * a_char_buf, enum CREncoding a_enc)
+{
+ CRParser *parser = NULL;
+
+ g_return_val_if_fail (a_char_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_char_buf, strlen ((const char *) a_char_buf),
+ a_enc, FALSE);
+ g_return_val_if_fail (parser, NULL);
+
+ return NULL;
+}
+
+/**
+ * cr_selector_append:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *@a_new: the instance of #CRSelector to be appended.
+ *
+ *Appends a new instance of #CRSelector to the current selector list.
+ *
+ *Returns the new list.
+ */
+CRSelector *
+cr_selector_append (CRSelector * a_this, CRSelector * a_new)
+{
+ CRSelector *cur = NULL;
+
+ if (!a_this) {
+ return a_new;
+ }
+
+ /*walk forward the list headed by a_this to get the list tail */
+ for (cur = a_this; cur && cur->next; cur = cur->next) ;
+
+ cur->next = a_new;
+ a_new->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_selector_prepend:
+ *
+ *@a_this: the current instance of #CRSelector list.
+ *@a_new: the instance of #CRSelector.
+ *
+ *Prepends an element to the #CRSelector list.
+ *
+ *Returns the new list.
+ */
+CRSelector *
+cr_selector_prepend (CRSelector * a_this, CRSelector * a_new)
+{
+ CRSelector *cur = NULL;
+
+ a_new->next = a_this;
+ a_this->prev = a_new;
+
+ for (cur = a_new; cur && cur->prev; cur = cur->prev) ;
+
+ return cur;
+}
+
+/**
+ * cr_selector_append_simple_sel:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *@a_simple_sel: the simple selector to append.
+ *
+ *append a simple selector to the current #CRSelector list.
+ *
+ *Returns the new list or NULL in case of failure.
+ */
+CRSelector *
+cr_selector_append_simple_sel (CRSelector * a_this,
+ CRSimpleSel * a_simple_sel)
+{
+ CRSelector *selector = NULL;
+
+ selector = cr_selector_new (a_simple_sel);
+ g_return_val_if_fail (selector, NULL);
+
+ return cr_selector_append (a_this, selector);
+}
+
+guchar *
+cr_selector_to_string (CRSelector const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ if (a_this) {
+ CRSelector const *cur = NULL;
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->simple_sel) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_simple_sel_to_string
+ (cur->simple_sel);
+
+ if (tmp_str) {
+ if (cur->prev)
+ g_string_append (str_buf,
+ ", ");
+
+ g_string_append (str_buf, (const gchar *) tmp_str);
+
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_selector_dump:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *@a_fp: the destination file.
+ *
+ *Serializes the current instance of #CRSelector to a file.
+ */
+void
+cr_selector_dump (CRSelector const * a_this, FILE * a_fp)
+{
+ guchar *tmp_buf = NULL;
+
+ if (a_this) {
+ tmp_buf = cr_selector_to_string (a_this);
+ if (tmp_buf) {
+ fprintf (a_fp, "%s", tmp_buf);
+ g_free (tmp_buf);
+ tmp_buf = NULL;
+ }
+ }
+}
+
+/**
+ * cr_selector_ref:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *
+ *Increments the ref count of the current instance
+ *of #CRSelector.
+ */
+void
+cr_selector_ref (CRSelector * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ * cr_selector_unref:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *
+ *Decrements the ref count of the current instance of
+ *#CRSelector.
+ *If the ref count reaches zero, the current instance of
+ *#CRSelector is destroyed.
+ *
+ *Returns TRUE if this function destroyed the current instance
+ *of #CRSelector, FALSE otherwise.
+ */
+gboolean
+cr_selector_unref (CRSelector * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_selector_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * cr_selector_destroy:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *
+ *Destroys the selector list.
+ */
+void
+cr_selector_destroy (CRSelector * a_this)
+{
+ CRSelector *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ /*
+ *go and get the list tail. In the same time, free
+ *all the simple selectors contained in the list.
+ */
+ for (cur = a_this; cur && cur->next; cur = cur->next) {
+ if (cur->simple_sel) {
+ cr_simple_sel_destroy (cur->simple_sel);
+ cur->simple_sel = NULL;
+ }
+ }
+
+ if (cur) {
+ if (cur->simple_sel) {
+ cr_simple_sel_destroy (cur->simple_sel);
+ cur->simple_sel = NULL;
+ }
+ }
+
+ /*in case the list has only one element */
+ if (cur && !cur->prev) {
+ g_free (cur);
+ return;
+ }
+
+ /*walk backward the list and free each "next element" */
+ for (cur = cur->prev; cur && cur->prev; cur = cur->prev) {
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+ }
+
+ if (!cur)
+ return;
+
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+
+ g_free (cur);
+}
diff --git a/src/st/croco/cr-selector.h b/src/st/croco/cr-selector.h
new file mode 100644
index 0000000..dd6a7f7
--- /dev/null
+++ b/src/st/croco/cr-selector.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_SELECTOR_H__
+#define __CR_SELECTOR_H__
+
+#include <stdio.h>
+#include "cr-utils.h"
+#include "cr-simple-sel.h"
+#include "cr-parsing-location.h"
+
+/**
+ *@file
+ *The declaration file of the #CRSelector file.
+ */
+
+G_BEGIN_DECLS
+
+typedef struct _CRSelector CRSelector ;
+
+/**
+ *Abstracts a CSS2 selector as defined in the right part
+ *of the 'ruleset" production in the appendix D.1 of the
+ *css2 spec.
+ *It is actually the abstraction of a comma separated list
+ *of simple selectors list.
+ *In a css2 file, a selector is a list of simple selectors
+ *separated by a comma.
+ *e.g: sel0, sel1, sel2 ...
+ *Each seln is a simple selector
+ */
+struct _CRSelector
+{
+ /**
+ *A Selection expression.
+ *It is a list of basic selectors.
+ *Each basic selector can be either an element
+ *selector, an id selector, a class selector, an
+ *attribute selector, an universal selector etc ...
+ */
+ CRSimpleSel *simple_sel ;
+
+ /**The next selector list element*/
+ CRSelector *next ;
+ CRSelector *prev ;
+ CRParsingLocation location ;
+ glong ref_count ;
+};
+
+CRSelector* cr_selector_new (CRSimpleSel *a_sel_expr) ;
+
+CRSelector * cr_selector_parse_from_buf (const guchar * a_char_buf,
+ enum CREncoding a_enc) ;
+
+CRSelector* cr_selector_append (CRSelector *a_this, CRSelector *a_new) ;
+
+CRSelector* cr_selector_append_simple_sel (CRSelector *a_this,
+ CRSimpleSel *a_simple_sel) ;
+
+CRSelector* cr_selector_prepend (CRSelector *a_this, CRSelector *a_new) ;
+
+guchar * cr_selector_to_string (CRSelector const *a_this) ;
+
+void cr_selector_dump (CRSelector const *a_this, FILE *a_fp) ;
+
+void cr_selector_ref (CRSelector *a_this) ;
+
+gboolean cr_selector_unref (CRSelector *a_this) ;
+
+void cr_selector_destroy (CRSelector *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_SELECTOR_H__*/
diff --git a/src/st/croco/cr-simple-sel.c b/src/st/croco/cr-simple-sel.c
new file mode 100644
index 0000000..bac8621
--- /dev/null
+++ b/src/st/croco/cr-simple-sel.c
@@ -0,0 +1,323 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include <glib.h>
+#include "cr-simple-sel.h"
+
+/**
+ * cr_simple_sel_new:
+ *
+ *The constructor of #CRSimpleSel.
+ *
+ *Returns the new instance of #CRSimpleSel.
+ */
+CRSimpleSel *
+cr_simple_sel_new (void)
+{
+ CRSimpleSel *result = NULL;
+
+ result = g_try_malloc (sizeof (CRSimpleSel));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRSimpleSel));
+
+ return result;
+}
+
+/**
+ * cr_simple_sel_append_simple_sel:
+ *
+ *Appends a simpe selector to the current list of simple selector.
+ *
+ *@a_this: the this pointer of the current instance of #CRSimpleSel.
+ *@a_sel: the simple selector to append.
+ *
+ *Returns: the new list upon successful completion, an error code otherwise.
+ */
+CRSimpleSel *
+cr_simple_sel_append_simple_sel (CRSimpleSel * a_this, CRSimpleSel * a_sel)
+{
+ CRSimpleSel *cur = NULL;
+
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL)
+ return a_sel;
+
+ for (cur = a_this; cur->next; cur = cur->next) ;
+
+ cur->next = a_sel;
+ a_sel->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_simple_sel_prepend_simple_sel:
+ *
+ *@a_this: the this pointer of the current instance of #CRSimpleSel.
+ *@a_sel: the simple selector to prepend.
+ *
+ *Prepends a simple selector to the current list of simple selectors.
+ *
+ *Returns the new list upon successful completion, an error code otherwise.
+ */
+CRSimpleSel *
+cr_simple_sel_prepend_simple_sel (CRSimpleSel * a_this, CRSimpleSel * a_sel)
+{
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL)
+ return a_sel;
+
+ a_sel->next = a_this;
+ a_this->prev = a_sel;
+
+ return a_sel;
+}
+
+guchar *
+cr_simple_sel_to_string (CRSimpleSel const * a_this)
+{
+ GString *str_buf = NULL;
+ guchar *result = NULL;
+
+ CRSimpleSel const *cur = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->name) {
+ guchar *str = (guchar *) g_strndup (cur->name->stryng->str,
+ cur->name->stryng->len);
+
+ if (str) {
+ switch (cur->combinator) {
+ case COMB_WS:
+ g_string_append (str_buf, " ");
+ break;
+
+ case COMB_PLUS:
+ g_string_append (str_buf, "+");
+ break;
+
+ case COMB_GT:
+ g_string_append (str_buf, ">");
+ break;
+
+ default:
+ break;
+ }
+
+ g_string_append (str_buf, (const gchar *) str);
+ g_free (str);
+ str = NULL;
+ }
+ }
+
+ if (cur->add_sel) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_additional_sel_to_string (cur->add_sel);
+ if (tmp_str) {
+ g_string_append (str_buf, (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+
+guchar *
+cr_simple_sel_one_to_string (CRSimpleSel const * a_this)
+{
+ GString *str_buf = NULL;
+ guchar *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ if (a_this->name) {
+ guchar *str = (guchar *) g_strndup (a_this->name->stryng->str,
+ a_this->name->stryng->len);
+
+ if (str) {
+ g_string_append_printf (str_buf, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+ }
+
+ if (a_this->add_sel) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_additional_sel_to_string (a_this->add_sel);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_simple_sel_dump:
+ *@a_this: the current instance of #CRSimpleSel.
+ *@a_fp: the destination file pointer.
+ *
+ *Dumps the selector to a file.
+ *TODO: add the support of unicode in the dump.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_simple_sel_dump (CRSimpleSel const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ g_return_val_if_fail (a_fp, CR_BAD_PARAM_ERROR);
+
+ if (a_this) {
+ tmp_str = cr_simple_sel_to_string (a_this);
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_simple_sel_compute_specificity:
+ *
+ *@a_this: the current instance of #CRSimpleSel
+ *
+ *Computes the selector (combinator separated list of simple selectors)
+ *as defined in the css2 spec in chapter 6.4.3
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_simple_sel_compute_specificity (CRSimpleSel * a_this)
+{
+ CRAdditionalSel const *cur_add_sel = NULL;
+ CRSimpleSel const *cur_sel = NULL;
+ gulong a = 0,
+ b = 0,
+ c = 0;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ for (cur_sel = a_this; cur_sel; cur_sel = cur_sel->next) {
+ if (cur_sel->type_mask & TYPE_SELECTOR) {
+ c++; /*hmmh, is this a new language ? */
+ } else if (!cur_sel->name
+ || !cur_sel->name->stryng
+ || !cur_sel->name->stryng->str) {
+ if (cur_sel->add_sel->type ==
+ PSEUDO_CLASS_ADD_SELECTOR) {
+ /*
+ *this is a pseudo element, and
+ *the spec says, "ignore pseudo elements".
+ */
+ continue;
+ }
+ }
+
+ for (cur_add_sel = cur_sel->add_sel;
+ cur_add_sel; cur_add_sel = cur_add_sel->next) {
+ switch (cur_add_sel->type) {
+ case ID_ADD_SELECTOR:
+ a++;
+ break;
+
+ case NO_ADD_SELECTOR:
+ continue;
+
+ default:
+ b++;
+ break;
+ }
+ }
+ }
+
+ /*we suppose a, b and c have 1 to 3 digits */
+ a_this->specificity = a * 1000000 + b * 1000 + c;
+
+ return CR_OK;
+}
+
+/**
+ * cr_simple_sel_destroy:
+ *
+ *@a_this: the this pointer of the current instance of #CRSimpleSel.
+ *
+ *The destructor of the current instance of
+ *#CRSimpleSel.
+ */
+void
+cr_simple_sel_destroy (CRSimpleSel * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->name) {
+ cr_string_destroy (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (a_this->add_sel) {
+ cr_additional_sel_destroy (a_this->add_sel);
+ a_this->add_sel = NULL;
+ }
+
+ if (a_this->next) {
+ cr_simple_sel_destroy (a_this->next);
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ }
+}
diff --git a/src/st/croco/cr-simple-sel.h b/src/st/croco/cr-simple-sel.h
new file mode 100644
index 0000000..72b15fd
--- /dev/null
+++ b/src/st/croco/cr-simple-sel.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+
+#ifndef __CR_SEL_H__
+#define __CR_SEL_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-additional-sel.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *the declaration of the #CRSimpleSel class.
+ *
+ */
+enum Combinator
+{
+ NO_COMBINATOR,
+ COMB_WS,/*whitespace: descendent*/
+ COMB_PLUS,/*'+': preceded by*/
+ COMB_GT/*greater than ('>'): child*/
+} ;
+
+enum SimpleSelectorType
+{
+ NO_SELECTOR_TYPE = 0,
+ UNIVERSAL_SELECTOR = 1,
+ TYPE_SELECTOR = 1 << 1
+} ;
+
+typedef struct _CRSimpleSel CRSimpleSel ;
+
+/**
+ *The abstraction of a css2 simple selection list
+ *as defined by the right part of the "selector" production in the
+ *appendix D.1 of the css2 spec.
+ *It is basically a list of simple selector, each
+ *simple selector being separated by a combinator.
+ *
+ *In the libcroco's implementation, each simple selector
+ *is made of at most two parts:
+ *
+ *1/An element name or 'type selector' (which can hold a '*' and
+ *then been called 'universal selector')
+ *
+ *2/An additional selector that "specializes" the preceding type or
+ *universal selector. The additional selector can be either
+ *an id selector, or a class selector, or an attribute selector.
+ */
+struct _CRSimpleSel
+{
+ enum SimpleSelectorType type_mask ;
+ gboolean is_case_sentive ;
+ CRString * name ;
+ /**
+ *The combinator that separates
+ *this simple selector from the previous
+ *one.
+ */
+ enum Combinator combinator ;
+
+ /**
+ *The additional selector list of the
+ *current simple selector.
+ *An additional selector may
+ *be a class selector, an id selector,
+ *or an attribute selector.
+ *Note that this field is a linked list.
+ */
+ CRAdditionalSel *add_sel ;
+
+ /*
+ *the specificity as specified by
+ *chapter 6.4.3 of the spec.
+ */
+ gulong specificity ;
+
+ CRSimpleSel *next ;
+ CRSimpleSel *prev ;
+ CRParsingLocation location ;
+} ;
+
+CRSimpleSel * cr_simple_sel_new (void) ;
+
+CRSimpleSel * cr_simple_sel_append_simple_sel (CRSimpleSel *a_this,
+ CRSimpleSel *a_sel) ;
+
+CRSimpleSel * cr_simple_sel_prepend_simple_sel (CRSimpleSel *a_this,
+ CRSimpleSel *a_sel) ;
+
+guchar * cr_simple_sel_to_string (CRSimpleSel const *a_this) ;
+
+guchar * cr_simple_sel_one_to_string (CRSimpleSel const * a_this) ;
+
+enum CRStatus cr_simple_sel_dump (CRSimpleSel const *a_this, FILE *a_fp) ;
+
+enum CRStatus cr_simple_sel_dump_attr_sel_list (CRSimpleSel const *a_this) ;
+
+enum CRStatus cr_simple_sel_compute_specificity (CRSimpleSel *a_this) ;
+
+void cr_simple_sel_destroy (CRSimpleSel *a_this) ;
+
+G_END_DECLS
+
+
+#endif /*__CR_SIMPLE_SEL_H__*/
diff --git a/src/st/croco/cr-statement.c b/src/st/croco/cr-statement.c
new file mode 100644
index 0000000..eaeb49f
--- /dev/null
+++ b/src/st/croco/cr-statement.c
@@ -0,0 +1,2784 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See COPYRIGHTS files for copyrights information.
+ */
+
+#include <string.h>
+#include "cr-statement.h"
+#include "cr-parser.h"
+
+/**
+ *@file
+ *Definition of the #CRStatement class.
+ */
+
+#define DECLARATION_INDENT_NB 2
+
+static void cr_statement_clear (CRStatement * a_this);
+
+static void
+parse_font_face_start_font_face_cb (CRDocHandler * a_this,
+ CRParsingLocation *a_location)
+{
+ CRStatement *stmt = NULL;
+ enum CRStatus status = CR_OK;
+
+ stmt = cr_statement_new_at_font_face_rule (NULL, NULL);
+ g_return_if_fail (stmt);
+
+ status = cr_doc_handler_set_ctxt (a_this, stmt);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_font_face_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ return;
+ }
+}
+
+static void
+parse_font_face_property_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_value, gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+ CRString *name = NULL;
+ CRDeclaration *decl = NULL;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ g_return_if_fail (a_this && a_name);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt);
+ g_return_if_fail (stmt->type == AT_FONT_FACE_RULE_STMT);
+
+ name = cr_string_dup (a_name) ;
+ g_return_if_fail (name);
+ decl = cr_declaration_new (stmt, name, a_value);
+ if (!decl) {
+ cr_utils_trace_info ("cr_declaration_new () failed.");
+ goto error;
+ }
+ name = NULL;
+
+ stmt->kind.font_face_rule->decl_list =
+ cr_declaration_append (stmt->kind.font_face_rule->decl_list,
+ decl);
+ if (!stmt->kind.font_face_rule->decl_list)
+ goto error;
+ decl = NULL;
+
+ error:
+ if (decl) {
+ cr_declaration_unref (decl);
+ decl = NULL;
+ }
+ if (name) {
+ cr_string_destroy (name);
+ name = NULL;
+ }
+}
+
+static void
+parse_font_face_end_font_face_cb (CRDocHandler * a_this)
+{
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this);
+
+ resultptr = &result;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) resultptr);
+ g_return_if_fail (status == CR_OK && result);
+ g_return_if_fail (result->type == AT_FONT_FACE_RULE_STMT);
+
+ status = cr_doc_handler_set_result (a_this, result);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_page_start_page_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRString * a_pseudo_page,
+ CRParsingLocation *a_location)
+{
+ CRStatement *stmt = NULL;
+ enum CRStatus status = CR_OK;
+ CRString *page_name = NULL, *pseudo_name = NULL ;
+
+ if (a_name)
+ page_name = cr_string_dup (a_name) ;
+ if (a_pseudo_page)
+ pseudo_name = cr_string_dup (a_pseudo_page) ;
+
+ stmt = cr_statement_new_at_page_rule (NULL, NULL,
+ page_name,
+ pseudo_name);
+ page_name = NULL ;
+ pseudo_name = NULL ;
+ g_return_if_fail (stmt);
+ status = cr_doc_handler_set_ctxt (a_this, stmt);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_page_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ }
+}
+
+static void
+parse_page_property_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_expression, gboolean a_important)
+{
+ CRString *name = NULL;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ CRDeclaration *decl = NULL;
+ enum CRStatus status = CR_OK;
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt->type == AT_PAGE_RULE_STMT);
+
+ name = cr_string_dup (a_name);
+ g_return_if_fail (name);
+
+ decl = cr_declaration_new (stmt, name, a_expression);
+ g_return_if_fail (decl);
+ decl->important = a_important;
+ stmt->kind.page_rule->decl_list =
+ cr_declaration_append (stmt->kind.page_rule->decl_list, decl);
+ g_return_if_fail (stmt->kind.page_rule->decl_list);
+}
+
+static void
+parse_page_end_page_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRString * a_pseudo_page)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt);
+ g_return_if_fail (stmt->type == AT_PAGE_RULE_STMT);
+
+ status = cr_doc_handler_set_result (a_this, stmt);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_start_media_cb (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *at_media = NULL;
+ GList *media_list = NULL;
+
+ g_return_if_fail (a_this && a_this->priv);
+
+ if (a_media_list) {
+ /*duplicate media list */
+ media_list = cr_utils_dup_glist_of_cr_string
+ (a_media_list);
+ }
+
+ g_return_if_fail (media_list);
+
+ /*make sure cr_statement_new_at_media_rule works in this case. */
+ at_media = cr_statement_new_at_media_rule (NULL, NULL, media_list);
+
+ status = cr_doc_handler_set_ctxt (a_this, at_media);
+ g_return_if_fail (status == CR_OK);
+ status = cr_doc_handler_set_result (a_this, at_media);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ g_return_if_fail (a_this);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ cr_doc_handler_set_result (a_this, NULL);
+ }
+}
+
+static void
+parse_at_media_start_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *at_media = NULL;
+ CRStatement **at_media_ptr = NULL;
+ CRStatement *ruleset = NULL;
+
+ g_return_if_fail (a_this && a_this->priv && a_sellist);
+
+ at_media_ptr = &at_media;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) at_media_ptr);
+ g_return_if_fail (status == CR_OK && at_media);
+ g_return_if_fail (at_media->type == AT_MEDIA_RULE_STMT);
+ ruleset = cr_statement_new_ruleset (NULL, a_sellist, NULL, at_media);
+ g_return_if_fail (ruleset);
+ status = cr_doc_handler_set_ctxt (a_this, ruleset);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_property_cb (CRDocHandler * a_this,
+ CRString * a_name, CRTerm * a_value,
+ gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+
+ /*
+ *the current ruleset stmt, child of the
+ *current at-media being parsed.
+ */
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ CRDeclaration *decl = NULL;
+ CRString *name = NULL;
+
+ g_return_if_fail (a_this && a_name);
+
+ name = cr_string_dup (a_name) ;
+ g_return_if_fail (name);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this,
+ (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt);
+ g_return_if_fail (stmt->type == RULESET_STMT);
+
+ decl = cr_declaration_new (stmt, name, a_value);
+ g_return_if_fail (decl);
+ decl->important = a_important;
+ status = cr_statement_ruleset_append_decl (stmt, decl);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_end_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ enum CRStatus status = CR_OK;
+
+ /*
+ *the current ruleset stmt, child of the
+ *current at-media being parsed.
+ */
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ g_return_if_fail (a_this && a_sellist);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt
+ && stmt->type == RULESET_STMT);
+ g_return_if_fail (stmt->kind.ruleset->parent_media_rule);
+
+ status = cr_doc_handler_set_ctxt
+ (a_this, stmt->kind.ruleset->parent_media_rule);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_end_media_cb (CRDocHandler * a_this,
+ GList * a_media_list)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *at_media = NULL;
+ CRStatement **at_media_ptr = NULL;
+
+ g_return_if_fail (a_this && a_this->priv);
+
+ at_media_ptr = &at_media;
+ status = cr_doc_handler_get_ctxt (a_this,
+ (gpointer *) at_media_ptr);
+ g_return_if_fail (status == CR_OK && at_media);
+ status = cr_doc_handler_set_result (a_this, at_media);
+}
+
+static void
+parse_ruleset_start_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ CRStatement *ruleset = NULL;
+
+ g_return_if_fail (a_this && a_this->priv && a_sellist);
+
+ ruleset = cr_statement_new_ruleset (NULL, a_sellist, NULL, NULL);
+ g_return_if_fail (ruleset);
+
+ cr_doc_handler_set_result (a_this, ruleset);
+}
+
+static void
+parse_ruleset_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ cr_doc_handler_set_result (a_this, NULL);
+ }
+}
+
+static void
+parse_ruleset_property_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_value, gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *ruleset = NULL;
+ CRStatement **rulesetptr = NULL;
+ CRDeclaration *decl = NULL;
+ CRString *stringue = NULL;
+
+ g_return_if_fail (a_this && a_this->priv && a_name);
+
+ stringue = cr_string_dup (a_name);
+ g_return_if_fail (stringue);
+
+ rulesetptr = &ruleset;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) rulesetptr);
+ g_return_if_fail (status == CR_OK
+ && ruleset
+ && ruleset->type == RULESET_STMT);
+
+ decl = cr_declaration_new (ruleset, stringue, a_value);
+ g_return_if_fail (decl);
+ decl->important = a_important;
+ status = cr_statement_ruleset_append_decl (ruleset, decl);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_ruleset_end_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this && a_sellist);
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) resultptr);
+
+ g_return_if_fail (status == CR_OK
+ && result
+ && result->type == RULESET_STMT);
+}
+
+static void
+cr_statement_clear (CRStatement * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case AT_RULE_STMT:
+ break;
+ case RULESET_STMT:
+ if (!a_this->kind.ruleset)
+ return;
+ if (a_this->kind.ruleset->sel_list) {
+ cr_selector_unref (a_this->kind.ruleset->sel_list);
+ a_this->kind.ruleset->sel_list = NULL;
+ }
+ if (a_this->kind.ruleset->decl_list) {
+ cr_declaration_destroy
+ (a_this->kind.ruleset->decl_list);
+ a_this->kind.ruleset->decl_list = NULL;
+ }
+ g_free (a_this->kind.ruleset);
+ a_this->kind.ruleset = NULL;
+ break;
+
+ case AT_IMPORT_RULE_STMT:
+ if (!a_this->kind.import_rule)
+ return;
+ if (a_this->kind.import_rule->url) {
+ cr_string_destroy
+ (a_this->kind.import_rule->url) ;
+ a_this->kind.import_rule->url = NULL;
+ }
+ g_free (a_this->kind.import_rule);
+ a_this->kind.import_rule = NULL;
+ break;
+
+ case AT_MEDIA_RULE_STMT:
+ if (!a_this->kind.media_rule)
+ return;
+ if (a_this->kind.media_rule->rulesets) {
+ cr_statement_destroy
+ (a_this->kind.media_rule->rulesets);
+ a_this->kind.media_rule->rulesets = NULL;
+ }
+ if (a_this->kind.media_rule->media_list) {
+ GList *cur = NULL;
+
+ for (cur = a_this->kind.media_rule->media_list;
+ cur; cur = cur->next) {
+ if (cur->data) {
+ cr_string_destroy ((CRString *) cur->data);
+ cur->data = NULL;
+ }
+
+ }
+ g_list_free (a_this->kind.media_rule->media_list);
+ a_this->kind.media_rule->media_list = NULL;
+ }
+ g_free (a_this->kind.media_rule);
+ a_this->kind.media_rule = NULL;
+ break;
+
+ case AT_PAGE_RULE_STMT:
+ if (!a_this->kind.page_rule)
+ return;
+
+ if (a_this->kind.page_rule->decl_list) {
+ cr_declaration_destroy
+ (a_this->kind.page_rule->decl_list);
+ a_this->kind.page_rule->decl_list = NULL;
+ }
+ if (a_this->kind.page_rule->name) {
+ cr_string_destroy
+ (a_this->kind.page_rule->name);
+ a_this->kind.page_rule->name = NULL;
+ }
+ if (a_this->kind.page_rule->pseudo) {
+ cr_string_destroy
+ (a_this->kind.page_rule->pseudo);
+ a_this->kind.page_rule->pseudo = NULL;
+ }
+ g_free (a_this->kind.page_rule);
+ a_this->kind.page_rule = NULL;
+ break;
+
+ case AT_CHARSET_RULE_STMT:
+ if (!a_this->kind.charset_rule)
+ return;
+
+ if (a_this->kind.charset_rule->charset) {
+ cr_string_destroy
+ (a_this->kind.charset_rule->charset);
+ a_this->kind.charset_rule->charset = NULL;
+ }
+ g_free (a_this->kind.charset_rule);
+ a_this->kind.charset_rule = NULL;
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ if (!a_this->kind.font_face_rule)
+ return;
+
+ if (a_this->kind.font_face_rule->decl_list) {
+ cr_declaration_unref
+ (a_this->kind.font_face_rule->decl_list);
+ a_this->kind.font_face_rule->decl_list = NULL;
+ }
+ g_free (a_this->kind.font_face_rule);
+ a_this->kind.font_face_rule = NULL;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * cr_statement_ruleset_to_string:
+ *
+ *@a_this: the current instance of #CRStatement
+ *@a_indent: the number of whitespace to use for indentation
+ *
+ *Serializes the ruleset statement into a string
+ *
+ *Returns the newly allocated serialised string. Must be freed
+ *by the caller, using g_free().
+ */
+static gchar *
+cr_statement_ruleset_to_string (CRStatement const * a_this, glong a_indent)
+{
+ GString *stringue = NULL;
+ gchar *tmp_str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT, NULL);
+
+ stringue = g_string_new (NULL);
+
+ if (a_this->kind.ruleset->sel_list) {
+ if (a_indent)
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+
+ tmp_str =
+ (gchar *) cr_selector_to_string (a_this->kind.ruleset->
+ sel_list);
+ if (tmp_str) {
+ g_string_append (stringue, tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ g_string_append (stringue, " {\n");
+ if (a_this->kind.ruleset->decl_list) {
+ tmp_str = (gchar *) cr_declaration_list_to_string2
+ (a_this->kind.ruleset->decl_list,
+ a_indent + DECLARATION_INDENT_NB, TRUE);
+ if (tmp_str) {
+ g_string_append (stringue, tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ g_string_append (stringue, "\n");
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ }
+ g_string_append (stringue, "}");
+ result = g_string_free (stringue, FALSE);
+
+ if (tmp_str) {
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ return result;
+}
+
+
+/**
+ * cr_statement_font_face_rule_to_string:
+ *
+ *@a_this: the current instance of #CRStatement to consider
+ *It must be a font face rule statement.
+ *@a_indent: the number of white spaces of indentation.
+ *
+ *Serializes a font face rule statement into a string.
+ *
+ *Returns the serialized string. Must be deallocated by the caller
+ *using g_free().
+ */
+static gchar *
+cr_statement_font_face_rule_to_string (CRStatement const * a_this,
+ glong a_indent)
+{
+ gchar *result = NULL, *tmp_str = NULL ;
+ GString *stringue = NULL ;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT,
+ NULL);
+
+ if (a_this->kind.font_face_rule->decl_list) {
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ if (a_indent)
+ cr_utils_dump_n_chars2 (' ', stringue,
+ a_indent);
+ g_string_append (stringue, "@font-face {\n");
+ tmp_str = (gchar *) cr_declaration_list_to_string2
+ (a_this->kind.font_face_rule->decl_list,
+ a_indent + DECLARATION_INDENT_NB, TRUE) ;
+ if (tmp_str) {
+ g_string_append (stringue,
+ tmp_str) ;
+ g_free (tmp_str) ;
+ tmp_str = NULL ;
+ }
+ g_string_append (stringue, "\n}");
+ }
+ if (stringue) {
+ result = g_string_free (stringue, FALSE);
+ stringue = NULL ;
+ }
+ return result ;
+}
+
+
+/**
+ * cr_statement_charset_to_string:
+ *
+ *Serialises an \@charset statement into a string.
+ *@a_this: the statement to serialize.
+ *@a_indent: the number of indentation spaces
+ *
+ *Returns the serialized charset statement. Must be
+ *freed by the caller using g_free().
+ */
+static gchar *
+cr_statement_charset_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ GString *stringue = NULL ;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_CHARSET_RULE_STMT,
+ NULL) ;
+
+ if (a_this->kind.charset_rule
+ && a_this->kind.charset_rule->charset
+ && a_this->kind.charset_rule->charset->stryng
+ && a_this->kind.charset_rule->charset->stryng->str) {
+ str = g_strndup (a_this->kind.charset_rule->charset->stryng->str,
+ a_this->kind.charset_rule->charset->stryng->len);
+ g_return_val_if_fail (str, NULL);
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ g_string_append_printf (stringue,
+ "@charset \"%s\" ;", str);
+ if (str) {
+ g_free (str);
+ str = NULL;
+ }
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE);
+ }
+ return str ;
+}
+
+
+/**
+ * cr_statement_at_page_rule_to_string:
+ *
+ *Serialises the at page rule statement into a string
+ *@a_this: the current instance of #CRStatement. Must
+ *be an "\@page" rule statement.
+ *
+ *Returns the serialized string. Must be freed by the caller
+ */
+static gchar *
+cr_statement_at_page_rule_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ GString *stringue = NULL;
+ gchar *result = NULL ;
+
+ stringue = g_string_new (NULL) ;
+
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent) ;
+ g_string_append (stringue, "@page");
+ if (a_this->kind.page_rule->name
+ && a_this->kind.page_rule->name->stryng) {
+ g_string_append_printf
+ (stringue, " %s",
+ a_this->kind.page_rule->name->stryng->str) ;
+ } else {
+ g_string_append (stringue, " ");
+ }
+ if (a_this->kind.page_rule->pseudo
+ && a_this->kind.page_rule->pseudo->stryng) {
+ g_string_append_printf
+ (stringue, " :%s",
+ a_this->kind.page_rule->pseudo->stryng->str) ;
+ }
+ if (a_this->kind.page_rule->decl_list) {
+ gchar *str = NULL ;
+ g_string_append (stringue, " {\n");
+ str = (gchar *) cr_declaration_list_to_string2
+ (a_this->kind.page_rule->decl_list,
+ a_indent + DECLARATION_INDENT_NB, TRUE) ;
+ if (str) {
+ g_string_append (stringue, str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+ g_string_append (stringue, "\n}\n");
+ }
+ result = g_string_free (stringue, FALSE) ;
+ stringue = NULL ;
+ return result ;
+}
+
+
+/**
+ *Serializes an \@media statement.
+ *@param a_this the current instance of #CRStatement
+ *@param a_indent the number of spaces of indentation.
+ *@return the serialized \@media statement. Must be freed
+ *by the caller using g_free().
+ */
+static gchar *
+cr_statement_media_rule_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ GString *stringue = NULL ;
+ GList const *cur = NULL;
+
+ g_return_val_if_fail (a_this->type == AT_MEDIA_RULE_STMT,
+ NULL);
+
+ if (a_this->kind.media_rule) {
+ stringue = g_string_new (NULL) ;
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ g_string_append (stringue, "@media");
+
+ for (cur = a_this->kind.media_rule->media_list; cur;
+ cur = cur->next) {
+ if (cur->data) {
+ gchar *str2 = cr_string_dup2
+ ((CRString const *) cur->data);
+
+ if (str2) {
+ if (cur->prev) {
+ g_string_append
+ (stringue,
+ ",");
+ }
+ g_string_append_printf
+ (stringue,
+ " %s", str2);
+ g_free (str2);
+ str2 = NULL;
+ }
+ }
+ }
+ g_string_append (stringue, " {\n");
+ str = cr_statement_list_to_string
+ (a_this->kind.media_rule->rulesets,
+ a_indent + DECLARATION_INDENT_NB) ;
+ if (str) {
+ g_string_append (stringue, str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+ g_string_append (stringue, "\n}");
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE) ;
+ }
+ return str ;
+}
+
+
+static gchar *
+cr_statement_import_rule_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ GString *stringue = NULL ;
+ gchar *str = NULL;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ NULL) ;
+
+ if (a_this->kind.import_rule->url
+ && a_this->kind.import_rule->url->stryng) {
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ str = g_strndup (a_this->kind.import_rule->url->stryng->str,
+ a_this->kind.import_rule->url->stryng->len);
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ if (str) {
+ g_string_append_printf (stringue,
+ "@import url(\"%s\")",
+ str);
+ g_free (str);
+ str = NULL ;
+ } else /*there is no url, so no import rule, get out! */
+ return NULL;
+
+ if (a_this->kind.import_rule->media_list) {
+ GList const *cur = NULL;
+
+ for (cur = a_this->kind.import_rule->media_list;
+ cur; cur = cur->next) {
+ if (cur->data) {
+ CRString const *crstr = cur->data;
+
+ if (cur->prev) {
+ g_string_append
+ (stringue, ", ");
+ }
+ if (crstr
+ && crstr->stryng
+ && crstr->stryng->str) {
+ g_string_append_len
+ (stringue,
+ crstr->stryng->str,
+ crstr->stryng->len) ;
+ }
+ }
+ }
+ }
+ g_string_append (stringue, " ;");
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE) ;
+ stringue = NULL ;
+ }
+ return str ;
+}
+
+
+/*******************
+ *public functions
+ ******************/
+
+/**
+ * cr_statement_does_buf_parses_against_core:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Tries to parse a buffer and says whether if the content of the buffer
+ *is a css statement as defined by the "Core CSS Grammar" (chapter 4 of the
+ *css spec) or not.
+ *
+ *Returns TRUE if the buffer parses against the core grammar, false otherwise.
+ */
+gboolean
+cr_statement_does_buf_parses_against_core (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ CRParser *parser = NULL;
+ enum CRStatus status = CR_OK;
+ gboolean result = FALSE;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ g_return_val_if_fail (parser, FALSE);
+
+ status = cr_parser_set_use_core_grammar (parser, TRUE);
+ if (status != CR_OK) {
+ goto cleanup;
+ }
+
+ status = cr_parser_parse_statement_core (parser);
+ if (status == CR_OK) {
+ result = TRUE;
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains a css statement and returns
+ *an instance of #CRStatement in case of successful parsing.
+ *TODO: at support of "\@import" rules.
+ *
+ *Returns the newly built instance of #CRStatement in case
+ *of successful parsing, NULL otherwise.
+ */
+CRStatement *
+cr_statement_parse_from_buf (const guchar * a_buf, enum CREncoding a_encoding)
+{
+ CRStatement *result = NULL;
+
+ /*
+ *The strategy of this function is "brute force".
+ *It tries to parse all the types of CRStatement it knows about.
+ *I could do this a smarter way but I don't have the time now.
+ *I think I will revisit this when time of performances and
+ *pull based incremental parsing comes.
+ */
+
+ result = cr_statement_ruleset_parse_from_buf (a_buf, a_encoding);
+ if (!result) {
+ result = cr_statement_at_charset_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_media_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_charset_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_font_face_rule_parse_from_buf
+ (a_buf, a_encoding);
+
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_page_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_import_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ out:
+ return result;
+}
+
+/**
+ * cr_statement_ruleset_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_enc: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains a ruleset statement an instantiates
+ *a #CRStatement of type RULESET_STMT.
+ *
+ *Returns the newly built instance of #CRStatement in case of successful parsing,
+ *NULL otherwise.
+ */
+CRStatement *
+cr_statement_ruleset_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ CRParser *parser = NULL;
+ CRDocHandler *sac_handler = NULL;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_enc, FALSE);
+
+ g_return_val_if_fail (parser, NULL);
+
+ sac_handler = cr_doc_handler_new ();
+ g_return_val_if_fail (parser, NULL);
+
+ sac_handler->start_selector = parse_ruleset_start_selector_cb;
+ sac_handler->end_selector = parse_ruleset_end_selector_cb;
+ sac_handler->property = parse_ruleset_property_cb;
+ sac_handler->unrecoverable_error =
+ parse_ruleset_unrecoverable_error_cb;
+
+ cr_parser_set_sac_handler (parser, sac_handler);
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ status = cr_parser_parse_ruleset (parser);
+ if (status != CR_OK) {
+ goto cleanup;
+ }
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ if (!((status == CR_OK) && result)) {
+ if (result) {
+ cr_statement_destroy (result);
+ result = NULL;
+ }
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_statement_new_ruleset:
+ *
+ *@a_sel_list: the list of #CRSimpleSel (selectors)
+ *the rule applies to.
+ *@a_decl_list: the list of instances of #CRDeclaration
+ *that composes the ruleset.
+ *@a_media_types: a list of instances of GString that
+ *describe the media list this ruleset applies to.
+ *
+ *Creates a new instance of #CRStatement of type
+ *#CRRulSet.
+ *
+ *Returns the new instance of #CRStatement or NULL if something
+ *went wrong.
+ */
+CRStatement *
+cr_statement_new_ruleset (CRStyleSheet * a_sheet,
+ CRSelector * a_sel_list,
+ CRDeclaration * a_decl_list,
+ CRStatement * a_parent_media_rule)
+{
+ CRStatement *result = NULL;
+
+ g_return_val_if_fail (a_sel_list, NULL);
+
+ if (a_parent_media_rule) {
+ g_return_val_if_fail
+ (a_parent_media_rule->type == AT_MEDIA_RULE_STMT,
+ NULL);
+ g_return_val_if_fail (a_parent_media_rule->kind.media_rule,
+ NULL);
+ }
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = RULESET_STMT;
+ result->kind.ruleset = g_try_malloc (sizeof (CRRuleSet));
+
+ if (!result->kind.ruleset) {
+ cr_utils_trace_info ("Out of memory");
+ if (result)
+ g_free (result);
+ return NULL;
+ }
+
+ memset (result->kind.ruleset, 0, sizeof (CRRuleSet));
+ result->kind.ruleset->sel_list = a_sel_list;
+ if (a_sel_list)
+ cr_selector_ref (a_sel_list);
+ result->kind.ruleset->decl_list = a_decl_list;
+
+ if (a_parent_media_rule) {
+ result->kind.ruleset->parent_media_rule = a_parent_media_rule;
+ a_parent_media_rule->kind.media_rule->rulesets =
+ cr_statement_append
+ (a_parent_media_rule->kind.media_rule->rulesets,
+ result);
+ }
+
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_media_rule_parse_from_buf:
+ *
+ *@a_buf: the input to parse.
+ *@a_enc: the encoding of the buffer.
+ *
+ *Parses a buffer that contains an "\@media" declaration
+ *and builds an \@media css statement.
+ *
+ *Returns the \@media statement, or NULL if the buffer could not
+ *be successfully parsed.
+ */
+CRStatement *
+cr_statement_at_media_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_enc)
+{
+ CRParser *parser = NULL;
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ CRDocHandler *sac_handler = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_enc, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of the parser failed");
+ goto cleanup;
+ }
+
+ sac_handler = cr_doc_handler_new ();
+ if (!sac_handler) {
+ cr_utils_trace_info
+ ("Instantiation of the sac handler failed");
+ goto cleanup;
+ }
+
+ sac_handler->start_media = parse_at_media_start_media_cb;
+ sac_handler->start_selector = parse_at_media_start_selector_cb;
+ sac_handler->property = parse_at_media_property_cb;
+ sac_handler->end_selector = parse_at_media_end_selector_cb;
+ sac_handler->end_media = parse_at_media_end_media_cb;
+ sac_handler->unrecoverable_error =
+ parse_at_media_unrecoverable_error_cb;
+
+ status = cr_parser_set_sac_handler (parser, sac_handler);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_media (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ if (status != CR_OK)
+ goto cleanup;
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_new_at_media_rule:
+ *
+ *@a_ruleset: the ruleset statements contained
+ *in the \@media rule.
+ *@a_media: the media string list. A list of GString pointers.
+ *
+ *Instantiates an instance of #CRStatement of type
+ *AT_MEDIA_RULE_STMT (\@media ruleset).
+ *
+ */
+CRStatement *
+cr_statement_new_at_media_rule (CRStyleSheet * a_sheet,
+ CRStatement * a_rulesets, GList * a_media)
+{
+ CRStatement *result = NULL,
+ *cur = NULL;
+
+ if (a_rulesets)
+ g_return_val_if_fail (a_rulesets->type == RULESET_STMT, NULL);
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_MEDIA_RULE_STMT;
+
+ result->kind.media_rule = g_try_malloc (sizeof (CRAtMediaRule));
+ if (!result->kind.media_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (result->kind.media_rule, 0, sizeof (CRAtMediaRule));
+ result->kind.media_rule->rulesets = a_rulesets;
+ for (cur = a_rulesets; cur; cur = cur->next) {
+ if (cur->type != RULESET_STMT || !cur->kind.ruleset) {
+ cr_utils_trace_info ("Bad parameter a_rulesets. "
+ "It should be a list of "
+ "correct ruleset statement only !");
+ goto error;
+ }
+ cur->kind.ruleset->parent_media_rule = result;
+ }
+
+ result->kind.media_rule->media_list = a_media;
+ if (a_sheet) {
+ cr_statement_set_parent_sheet (result, a_sheet);
+ }
+
+ return result;
+
+ error:
+ return NULL;
+}
+
+/**
+ * cr_statement_new_at_import_rule:
+ *
+ *@a_url: the url to connect to the get the file
+ *to be imported.
+ *@a_sheet: the imported parsed stylesheet.
+ *
+ *Creates a new instance of #CRStatment of type
+ *#CRAtImportRule.
+ *
+ *Returns the newly built instance of #CRStatement.
+ */
+CRStatement *
+cr_statement_new_at_import_rule (CRStyleSheet * a_container_sheet,
+ CRString * a_url,
+ GList * a_media_list,
+ CRStyleSheet * a_imported_sheet)
+{
+ CRStatement *result = NULL;
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_IMPORT_RULE_STMT;
+
+ result->kind.import_rule = g_try_malloc (sizeof (CRAtImportRule));
+
+ if (!result->kind.import_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+
+ memset (result->kind.import_rule, 0, sizeof (CRAtImportRule));
+ result->kind.import_rule->url = a_url;
+ result->kind.import_rule->media_list = a_media_list;
+ result->kind.import_rule->sheet = a_imported_sheet;
+ if (a_container_sheet)
+ cr_statement_set_parent_sheet (result, a_container_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_import_rule_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the encoding of a_buf.
+ *
+ *Parses a buffer that contains an "\@import" rule and
+ *instantiate a #CRStatement of type AT_IMPORT_RULE_STMT
+ *
+ *Returns the newly built instance of #CRStatement in case of
+ *a successful parsing, NULL otherwise.
+ */
+CRStatement *
+cr_statement_at_import_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ enum CRStatus status = CR_OK;
+ CRParser *parser = NULL;
+ CRStatement *result = NULL;
+ GList *media_list = NULL;
+ CRString *import_string = NULL;
+ CRParsingLocation location = {0} ;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of parser failed.");
+ goto cleanup;
+ }
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_import (parser,
+ &media_list,
+ &import_string,
+ &location);
+ if (status != CR_OK || !import_string)
+ goto cleanup;
+
+ result = cr_statement_new_at_import_rule (NULL, import_string,
+ media_list, NULL);
+ if (result) {
+ cr_parsing_location_copy (&result->location,
+ &location) ;
+ import_string = NULL;
+ media_list = NULL;
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+ if (media_list) {
+ for (; media_list;
+ media_list = g_list_next (media_list)) {
+ if (media_list->data) {
+ cr_string_destroy ((CRString*)media_list->data);
+ media_list->data = NULL;
+ }
+ }
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+ if (import_string) {
+ cr_string_destroy (import_string);
+ import_string = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_new_at_page_rule:
+ *
+ *@a_decl_list: a list of instances of #CRDeclarations
+ *which is actually the list of declarations that applies to
+ *this page rule.
+ *@a_selector: the page rule selector.
+ *
+ *Creates a new instance of #CRStatement of type
+ *#CRAtPageRule.
+ *
+ *Returns the newly built instance of #CRStatement or NULL
+ *in case of error.
+ */
+CRStatement *
+cr_statement_new_at_page_rule (CRStyleSheet * a_sheet,
+ CRDeclaration * a_decl_list,
+ CRString * a_name, CRString * a_pseudo)
+{
+ CRStatement *result = NULL;
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_PAGE_RULE_STMT;
+
+ result->kind.page_rule = g_try_malloc (sizeof (CRAtPageRule));
+
+ if (!result->kind.page_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+
+ memset (result->kind.page_rule, 0, sizeof (CRAtPageRule));
+ if (a_decl_list) {
+ result->kind.page_rule->decl_list = a_decl_list;
+ cr_declaration_ref (a_decl_list);
+ }
+ result->kind.page_rule->name = a_name;
+ result->kind.page_rule->pseudo = a_pseudo;
+ if (a_sheet)
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_page_rule_parse_from_buf:
+ *
+ *@a_buf: the character buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains an "\@page" production and,
+ *if the parsing succeeds, builds the page statement.
+ *
+ *Returns the newly built at page statement in case of successful parsing,
+ *NULL otherwise.
+ */
+CRStatement *
+cr_statement_at_page_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ enum CRStatus status = CR_OK;
+ CRParser *parser = NULL;
+ CRDocHandler *sac_handler = NULL;
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of the parser failed.");
+ goto cleanup;
+ }
+
+ sac_handler = cr_doc_handler_new ();
+ if (!sac_handler) {
+ cr_utils_trace_info
+ ("Instantiation of the sac handler failed.");
+ goto cleanup;
+ }
+
+ sac_handler->start_page = parse_page_start_page_cb;
+ sac_handler->property = parse_page_property_cb;
+ sac_handler->end_page = parse_page_end_page_cb;
+ sac_handler->unrecoverable_error = parse_page_unrecoverable_error_cb;
+
+ status = cr_parser_set_sac_handler (parser, sac_handler);
+ if (status != CR_OK)
+ goto cleanup;
+
+ /*Now, invoke the parser to parse the "@page production" */
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+ status = cr_parser_parse_page (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_statement_new_at_charset_rule:
+ *
+ *@a_charset: the string representing the charset.
+ *Note that the newly built instance of #CRStatement becomes
+ *the owner of a_charset. The caller must not free a_charset !!!.
+ *
+ *Creates a new instance of #CRStatement of type
+ *#CRAtCharsetRule.
+ *
+ *Returns the newly built instance of #CRStatement or NULL
+ *if an error arises.
+ */
+CRStatement *
+cr_statement_new_at_charset_rule (CRStyleSheet * a_sheet,
+ CRString * a_charset)
+{
+ CRStatement *result = NULL;
+
+ g_return_val_if_fail (a_charset, NULL);
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_CHARSET_RULE_STMT;
+
+ result->kind.charset_rule = g_try_malloc (sizeof (CRAtCharsetRule));
+
+ if (!result->kind.charset_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (result->kind.charset_rule, 0, sizeof (CRAtCharsetRule));
+ result->kind.charset_rule->charset = a_charset;
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_charset_rule_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of the buffer.
+ *
+ *Parses a buffer that contains an '\@charset' rule and
+ *creates an instance of #CRStatement of type AT_CHARSET_RULE_STMT.
+ *
+ *Returns the newly built instance of #CRStatement.
+ */
+CRStatement *
+cr_statement_at_charset_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ enum CRStatus status = CR_OK;
+ CRParser *parser = NULL;
+ CRStatement *result = NULL;
+ CRString *charset = NULL;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of the parser failed.");
+ goto cleanup;
+ }
+
+ /*Now, invoke the parser to parse the "@charset production" */
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+ status = cr_parser_parse_charset (parser, &charset, NULL);
+ if (status != CR_OK || !charset)
+ goto cleanup;
+
+ result = cr_statement_new_at_charset_rule (NULL, charset);
+ if (result)
+ charset = NULL;
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+ if (charset) {
+ cr_string_destroy (charset);
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_new_at_font_face_rule:
+ *
+ *@a_font_decls: a list of instances of #CRDeclaration. Each declaration
+ *is actually a font declaration.
+ *
+ *Creates an instance of #CRStatement of type #CRAtFontFaceRule.
+ *
+ *Returns the newly built instance of #CRStatement.
+ */
+CRStatement *
+cr_statement_new_at_font_face_rule (CRStyleSheet * a_sheet,
+ CRDeclaration * a_font_decls)
+{
+ CRStatement *result = NULL;
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_FONT_FACE_RULE_STMT;
+
+ result->kind.font_face_rule = g_try_malloc
+ (sizeof (CRAtFontFaceRule));
+
+ if (!result->kind.font_face_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (result->kind.font_face_rule, 0, sizeof (CRAtFontFaceRule));
+
+ result->kind.font_face_rule->decl_list = a_font_decls;
+ if (a_sheet)
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_font_face_rule_parse_from_buf:
+ *
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains an "\@font-face" rule and builds
+ *an instance of #CRStatement of type AT_FONT_FACE_RULE_STMT out of it.
+ *
+ *Returns the newly built instance of #CRStatement in case of successufull
+ *parsing, NULL otherwise.
+ */
+CRStatement *
+cr_statement_font_face_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ CRParser *parser = NULL;
+ CRDocHandler *sac_handler = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser)
+ goto cleanup;
+
+ sac_handler = cr_doc_handler_new ();
+ if (!sac_handler)
+ goto cleanup;
+
+ /*
+ *set sac callbacks here
+ */
+ sac_handler->start_font_face = parse_font_face_start_font_face_cb;
+ sac_handler->property = parse_font_face_property_cb;
+ sac_handler->end_font_face = parse_font_face_end_font_face_cb;
+ sac_handler->unrecoverable_error =
+ parse_font_face_unrecoverable_error_cb;
+
+ status = cr_parser_set_sac_handler (parser, sac_handler);
+ if (status != CR_OK)
+ goto cleanup;
+
+ /*
+ *cleanup spaces of comment that may be there before the real
+ *"@font-face" thing.
+ */
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_font_face (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ if (status != CR_OK || !result)
+ goto cleanup;
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_statement_set_parent_sheet:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_sheet: the sheet that contains the current statement.
+ *
+ *Sets the container stylesheet.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_set_parent_sheet (CRStatement * a_this, CRStyleSheet * a_sheet)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ a_this->parent_sheet = a_sheet;
+ return CR_OK;
+}
+
+/**
+ * cr_statement_get_parent_sheet:
+ *
+ *@a_this: the current #CRStatement.
+ *@a_sheet: out parameter. A pointer to the sheets that
+ *
+ *Gets the sheets that contains the current statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_get_parent_sheet (CRStatement * a_this, CRStyleSheet ** a_sheet)
+{
+ g_return_val_if_fail (a_this && a_sheet, CR_BAD_PARAM_ERROR);
+ *a_sheet = a_this->parent_sheet;
+ return CR_OK;
+}
+
+/**
+ * cr_statement_append:
+ *
+ *@a_this: the current instance of the statement list.
+ *@a_new: a_new the new instance of #CRStatement to append.
+ *
+ *Appends a new statement to the statement list.
+ *
+ *Returns the new list statement list, or NULL in cas of failure.
+ */
+CRStatement *
+cr_statement_append (CRStatement * a_this, CRStatement * a_new)
+{
+ CRStatement *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this) {
+ return a_new;
+ }
+
+ /*walk forward in the current list to find the tail list element */
+ for (cur = a_this; cur && cur->next; cur = cur->next) ;
+
+ cur->next = a_new;
+ a_new->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_statement_prepend:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_new: the new statement to prepend.
+ *
+ *Prepends the an instance of #CRStatement to
+ *the current statement list.
+ *
+ *Returns the new list with the new statement prepended,
+ *or NULL in case of an error.
+ */
+CRStatement *
+cr_statement_prepend (CRStatement * a_this, CRStatement * a_new)
+{
+ CRStatement *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this)
+ return a_new;
+
+ a_new->next = a_this;
+ a_this->prev = a_new;
+
+ /*walk backward in the prepended list to find the head list element */
+ for (cur = a_new; cur && cur->prev; cur = cur->prev) ;
+
+ return cur;
+}
+
+/**
+ * cr_statement_unlink:
+ *
+ *@a_this: the current statements list.
+ *@a_to_unlink: the statement to unlink from the list.
+ *
+ *Unlinks a statement from the statements list.
+ *
+ *Returns the new list where a_to_unlink has been unlinked
+ *from, or NULL in case of error.
+ */
+CRStatement *
+cr_statement_unlink (CRStatement * a_stmt)
+{
+ CRStatement *result = a_stmt;
+
+ g_return_val_if_fail (result, NULL);
+
+ /**
+ *Some sanity checks first
+ */
+ if (a_stmt->next) {
+ g_return_val_if_fail (a_stmt->next->prev == a_stmt, NULL);
+ }
+ if (a_stmt->prev) {
+ g_return_val_if_fail (a_stmt->prev->next == a_stmt, NULL);
+ }
+
+ /**
+ *Now, the real unlinking job.
+ */
+ if (a_stmt->next) {
+ a_stmt->next->prev = a_stmt->prev;
+ }
+ if (a_stmt->prev) {
+ a_stmt->prev->next = a_stmt->next;
+ }
+
+ if (a_stmt->parent_sheet
+ && a_stmt->parent_sheet->statements == a_stmt) {
+ a_stmt->parent_sheet->statements =
+ a_stmt->parent_sheet->statements->next;
+ }
+
+ a_stmt->next = NULL;
+ a_stmt->prev = NULL;
+ a_stmt->parent_sheet = NULL;
+
+ return result;
+}
+
+/**
+ * cr_statement_nr_rules:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *
+ *Gets the number of rules in the statement list;
+ *
+ *Returns number of rules in the statement list.
+ */
+gint
+cr_statement_nr_rules (CRStatement const * a_this)
+{
+ CRStatement const *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, -1);
+
+ for (cur = a_this; cur; cur = cur->next)
+ nr++;
+ return nr;
+}
+
+/**
+ * cr_statement_get_from_list:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@itemnr: the index into the statement list.
+ *
+ *Use an index to get a CRStatement from the statement list.
+ *
+ *Returns CRStatement at position itemnr, if itemnr > number of statements - 1,
+ *it will return NULL.
+ */
+CRStatement *
+cr_statement_get_from_list (CRStatement * a_this, int itemnr)
+{
+ CRStatement *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ for (cur = a_this; cur; cur = cur->next)
+ if (nr++ == itemnr)
+ return cur;
+ return NULL;
+}
+
+/**
+ * cr_statement_ruleset_set_sel_list:
+ *
+ *@a_this: the current ruleset statement.
+ *@a_sel_list: the selector list to set. Note
+ *that this function increments the ref count of a_sel_list.
+ *The sel list will be destroyed at the destruction of the
+ *current instance of #CRStatement.
+ *
+ *Sets a selector list to a ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_set_sel_list (CRStatement * a_this,
+ CRSelector * a_sel_list)
+{
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.ruleset->sel_list)
+ cr_selector_unref (a_this->kind.ruleset->sel_list);
+
+ a_this->kind.ruleset->sel_list = a_sel_list;
+
+ if (a_sel_list)
+ cr_selector_ref (a_sel_list);
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_get_declarations:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_decl_list: out parameter. A pointer to the the returned
+ *list of declaration. Must not be NULL.
+ *
+ *Gets a pointer to the list of declaration contained
+ *in the ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code if something
+ *bad happened.
+ */
+enum CRStatus
+cr_statement_ruleset_get_declarations (CRStatement * a_this,
+ CRDeclaration ** a_decl_list)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset
+ && a_decl_list, CR_BAD_PARAM_ERROR);
+
+ *a_decl_list = a_this->kind.ruleset->decl_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_get_sel_list:
+ *
+ *@a_this: the current ruleset statement.
+ *@a_list: out parameter. The returned selector list,
+ *if and only if the function returned CR_OK.
+ *
+ *Gets a pointer to the selector list contained in
+ *the current ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_get_sel_list (CRStatement const * a_this, CRSelector ** a_list)
+{
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ *a_list = a_this->kind.ruleset->sel_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_set_decl_list:
+ *
+ *@a_this: the current ruleset statement.
+ *@a_list: the declaration list to be added to the current
+ *ruleset statement.
+ *
+ *Sets a declaration list to the current ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_set_decl_list (CRStatement * a_this,
+ CRDeclaration * a_list)
+{
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.ruleset->decl_list == a_list)
+ return CR_OK;
+
+ if (a_this->kind.ruleset->sel_list) {
+ cr_declaration_destroy (a_this->kind.ruleset->decl_list);
+ }
+
+ a_this->kind.ruleset->sel_list = NULL;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_append_decl2:
+ *
+ *@a_this: the current statement.
+ *@a_prop: the property of the declaration.
+ *@a_value: the value of the declaration.
+ *
+ *Appends a declaration to the current ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_append_decl2 (CRStatement * a_this,
+ CRString * a_prop,
+ CRTerm * a_value)
+{
+ CRDeclaration *new_decls = NULL;
+
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ new_decls = cr_declaration_append2
+ (a_this->kind.ruleset->decl_list,
+ a_prop, a_value);
+ g_return_val_if_fail (new_decls, CR_ERROR);
+ a_this->kind.ruleset->decl_list = new_decls;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_append_decl:
+ *
+ *Appends a declaration to the current statement.
+ *
+ *@a_this: the current statement.
+ *@a_declaration: the declaration to append.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_append_decl (CRStatement * a_this,
+ CRDeclaration * a_decl)
+{
+ CRDeclaration *new_decls = NULL;
+
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ new_decls = cr_declaration_append
+ (a_this->kind.ruleset->decl_list, a_decl);
+ g_return_val_if_fail (new_decls, CR_ERROR);
+ a_this->kind.ruleset->decl_list = new_decls;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_import_rule_set_imported_sheet:
+ *
+ *Sets a stylesheet to the current \@import rule.
+ *@a_this: the current \@import rule.
+ *@a_sheet: the stylesheet. The stylesheet is owned
+ *by the current instance of #CRStatement, that is, the
+ *stylesheet will be destroyed when the current instance
+ *of #CRStatement is destroyed.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_set_imported_sheet (CRStatement * a_this,
+ CRStyleSheet * a_sheet)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+
+ a_this->kind.import_rule->sheet = a_sheet;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_import_rule_get_imported_sheet:
+ *
+ *@a_this: the current \@import rule statement.
+ *@a_sheet: out parameter. The returned stylesheet if and
+ *only if the function returns CR_OK.
+ *
+ *Gets the stylesheet contained by the \@import rule statement.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_get_imported_sheet (CRStatement * a_this,
+ CRStyleSheet ** a_sheet)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+ *a_sheet = a_this->kind.import_rule->sheet;
+ return CR_OK;
+
+}
+
+/**
+ * cr_statement_at_import_rule_set_url:
+ *
+ *@a_this: the current \@import rule statement.
+ *@a_url: the url to set.
+ *
+ *Sets an url to the current \@import rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_set_url (CRStatement * a_this,
+ CRString * a_url)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.import_rule->url) {
+ cr_string_destroy (a_this->kind.import_rule->url);
+ }
+
+ a_this->kind.import_rule->url = a_url;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_import_rule_get_url:
+ *
+ *@a_this: the current \@import rule statement.
+ *@a_url: out parameter. The returned url if
+ *and only if the function returned CR_OK.
+ *
+ *Gets the url of the \@import rule statement.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_get_url (CRStatement const * a_this,
+ CRString ** a_url)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+
+ *a_url = a_this->kind.import_rule->url;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_media_nr_rules:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *
+ *Returns the number of rules in the media rule;
+ */
+int
+cr_statement_at_media_nr_rules (CRStatement const * a_this)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_MEDIA_RULE_STMT
+ && a_this->kind.media_rule, CR_BAD_PARAM_ERROR);
+
+ return cr_statement_nr_rules (a_this->kind.media_rule->rulesets);
+}
+
+/**
+ * cr_statement_at_media_get_from_list:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@itemnr: the index into the media rule list of rules.
+ *
+ *Use an index to get a CRStatement from the media rule list of rules.
+ *
+ *Returns CRStatement at position itemnr, if itemnr > number of rules - 1,
+ *it will return NULL.
+ */
+CRStatement *
+cr_statement_at_media_get_from_list (CRStatement * a_this, int itemnr)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_MEDIA_RULE_STMT
+ && a_this->kind.media_rule, NULL);
+
+ return cr_statement_get_from_list (a_this->kind.media_rule->rulesets,
+ itemnr);
+}
+
+/**
+ * cr_statement_at_page_rule_set_declarations:
+ *
+ *@a_this: the current \@page rule statement.
+ *@a_decl_list: the declaration list to add. Will be freed
+ *by the current instance of #CRStatement when it is destroyed.
+ *
+ *Sets a declaration list to the current \@page rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_page_rule_set_declarations (CRStatement * a_this,
+ CRDeclaration * a_decl_list)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_PAGE_RULE_STMT
+ && a_this->kind.page_rule, CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.page_rule->decl_list) {
+ cr_declaration_unref (a_this->kind.page_rule->decl_list);
+ }
+
+ a_this->kind.page_rule->decl_list = a_decl_list;
+
+ if (a_decl_list) {
+ cr_declaration_ref (a_decl_list);
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_page_rule_get_declarations:
+ *
+ *@a_this: the current \@page rule statement.
+ *@a_decl_list: out parameter. The returned declaration list.
+ *
+ *Gets the declaration list associated to the current \@page rule
+ *statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_page_rule_get_declarations (CRStatement * a_this,
+ CRDeclaration ** a_decl_list)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_PAGE_RULE_STMT
+ && a_this->kind.page_rule, CR_BAD_PARAM_ERROR);
+
+ *a_decl_list = a_this->kind.page_rule->decl_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_charset_rule_set_charset:
+ *
+ *
+ *@a_this: the current \@charset rule statement.
+ *@a_charset: the charset to set.
+ *
+ *Sets the charset of the current \@charset rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_charset_rule_set_charset (CRStatement * a_this,
+ CRString * a_charset)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_CHARSET_RULE_STMT
+ && a_this->kind.charset_rule,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.charset_rule->charset) {
+ cr_string_destroy (a_this->kind.charset_rule->charset);
+ }
+ a_this->kind.charset_rule->charset = a_charset;
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_charset_rule_get_charset:
+ *@a_this: the current \@charset rule statement.
+ *@a_charset: out parameter. The returned charset string if
+ *and only if the function returned CR_OK.
+ *
+ *Gets the charset string associated to the current
+ *\@charset rule statement.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_charset_rule_get_charset (CRStatement const * a_this,
+ CRString ** a_charset)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_CHARSET_RULE_STMT
+ && a_this->kind.charset_rule,
+ CR_BAD_PARAM_ERROR);
+
+ *a_charset = a_this->kind.charset_rule->charset;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_font_face_rule_set_decls:
+ *
+ *@a_this: the current \@font-face rule statement.
+ *@a_decls: the declarations list to set.
+ *
+ *Sets a declaration list to the current \@font-face rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_font_face_rule_set_decls (CRStatement * a_this,
+ CRDeclaration * a_decls)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT
+ && a_this->kind.font_face_rule,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.font_face_rule->decl_list) {
+ cr_declaration_unref (a_this->kind.font_face_rule->decl_list);
+ }
+
+ a_this->kind.font_face_rule->decl_list = a_decls;
+ cr_declaration_ref (a_decls);
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_font_face_rule_get_decls:
+ *
+ *@a_this: the current \@font-face rule statement.
+ *@a_decls: out parameter. The returned declaration list if
+ *and only if this function returns CR_OK.
+ *
+ *Gets the declaration list associated to the current instance
+ *of \@font-face rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_font_face_rule_get_decls (CRStatement * a_this,
+ CRDeclaration ** a_decls)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT
+ && a_this->kind.font_face_rule,
+ CR_BAD_PARAM_ERROR);
+
+ *a_decls = a_this->kind.font_face_rule->decl_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_font_face_rule_add_decl:
+ *
+ *@a_this: the current \@font-face rule statement.
+ *@a_prop: the property of the declaration.
+ *@a_value: the value of the declaration.
+ *
+ *Adds a declaration to the current \@font-face rule
+ *statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_font_face_rule_add_decl (CRStatement * a_this,
+ CRString * a_prop, CRTerm * a_value)
+{
+ CRDeclaration *decls = NULL;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT
+ && a_this->kind.font_face_rule,
+ CR_BAD_PARAM_ERROR);
+
+ decls = cr_declaration_append2
+ (a_this->kind.font_face_rule->decl_list,
+ a_prop, a_value);
+
+ g_return_val_if_fail (decls, CR_ERROR);
+
+ if (a_this->kind.font_face_rule->decl_list == NULL)
+ cr_declaration_ref (decls);
+
+ a_this->kind.font_face_rule->decl_list = decls;
+
+ return CR_OK;
+}
+
+
+/**
+ * cr_statement_to_string:
+ *
+ *@a_this: the current statement to serialize
+ *@a_indent: the number of white space of indentation.
+ *
+ *Serializes a css statement into a string
+ *
+ *Returns the serialized statement. Must be freed by the caller
+ *using g_free().
+ */
+gchar *
+cr_statement_to_string (CRStatement const * a_this, gulong a_indent)
+{
+ gchar *str = NULL ;
+
+ if (!a_this)
+ return NULL;
+
+ switch (a_this->type) {
+ case RULESET_STMT:
+ str = cr_statement_ruleset_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ str = cr_statement_font_face_rule_to_string
+ (a_this, a_indent) ;
+ break;
+
+ case AT_CHARSET_RULE_STMT:
+ str = cr_statement_charset_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_PAGE_RULE_STMT:
+ str = cr_statement_at_page_rule_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_MEDIA_RULE_STMT:
+ str = cr_statement_media_rule_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_IMPORT_RULE_STMT:
+ str = cr_statement_import_rule_to_string
+ (a_this, a_indent);
+ break;
+
+ default:
+ cr_utils_trace_info ("Statement unrecognized");
+ break;
+ }
+ return str ;
+}
+
+gchar*
+cr_statement_list_to_string (CRStatement const *a_this, gulong a_indent)
+{
+ CRStatement const *cur_stmt = NULL ;
+ GString *stringue = NULL ;
+ gchar *str = NULL ;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ stringue = g_string_new (NULL) ;
+ if (!stringue) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ for (cur_stmt = a_this ; cur_stmt;
+ cur_stmt = cur_stmt->next) {
+ str = cr_statement_to_string (cur_stmt, a_indent) ;
+ if (str) {
+ if (!cur_stmt->prev) {
+ g_string_append (stringue, str) ;
+ } else {
+ g_string_append_printf
+ (stringue, "\n%s", str) ;
+ }
+ g_free (str) ;
+ str = NULL ;
+ }
+ }
+ str = g_string_free (stringue, FALSE) ;
+ return str ;
+}
+
+/**
+ * cr_statement_dump:
+ *
+ *@a_this: the current css2 statement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of white space indentation characters.
+ *
+ *Dumps the css2 statement to a file.
+ */
+void
+cr_statement_dump (CRStatement const * a_this, FILE * a_fp, gulong a_indent)
+{
+ gchar *str = NULL ;
+
+ if (!a_this)
+ return;
+
+ str = cr_statement_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s",str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_dump_ruleset:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white spaces to add.
+ *
+ *Dumps a ruleset statement to a file.
+ */
+void
+cr_statement_dump_ruleset (CRStatement const * a_this, FILE * a_fp, glong a_indent)
+{
+ gchar *str = NULL;
+
+ g_return_if_fail (a_fp && a_this);
+ str = cr_statement_ruleset_to_string (a_this, a_indent);
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+}
+
+/**
+ * cr_statement_dump_font_face_rule:
+ *
+ *@a_this: the current instance of font face rule statement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of white space indentation.
+ *
+ *Dumps a font face rule statement to a file.
+ */
+void
+cr_statement_dump_font_face_rule (CRStatement const * a_this, FILE * a_fp,
+ glong a_indent)
+{
+ gchar *str = NULL ;
+ g_return_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT);
+
+ str = cr_statement_font_face_rule_to_string (a_this,
+ a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_dump_charset:
+ *
+ *@a_this: the current instance of the \@charset rule statement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white spaces.
+ *
+ *Dumps an \@charset rule statement to a file.
+ */
+void
+cr_statement_dump_charset (CRStatement const * a_this, FILE * a_fp, gulong a_indent)
+{
+ gchar *str = NULL;
+
+ g_return_if_fail (a_this && a_this->type == AT_CHARSET_RULE_STMT);
+
+ str = cr_statement_charset_to_string (a_this,
+ a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+
+/**
+ * cr_statement_dump_page:
+ *
+ *@a_this: the statement to dump on stdout.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white spaces.
+ *
+ *Dumps an \@page rule statement on stdout.
+ */
+void
+cr_statement_dump_page (CRStatement const * a_this, FILE * a_fp, gulong a_indent)
+{
+ gchar *str = NULL;
+
+ g_return_if_fail (a_this
+ && a_this->type == AT_PAGE_RULE_STMT
+ && a_this->kind.page_rule);
+
+ str = cr_statement_at_page_rule_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+
+/**
+ * cr_statement_dump_media_rule:
+ *
+ *@a_this: the statement to dump.
+ *@a_fp: the destination file pointer
+ *@a_indent: the number of white spaces indentation.
+ *
+ *Dumps an \@media rule statement to a file.
+ */
+void
+cr_statement_dump_media_rule (CRStatement const * a_this,
+ FILE * a_fp,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ g_return_if_fail (a_this->type == AT_MEDIA_RULE_STMT);
+
+ str = cr_statement_media_rule_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_dump_import_rule:
+ *
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of white space indentations.
+ *
+ *Dumps an \@import rule statement to a file.
+ */
+void
+cr_statement_dump_import_rule (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ g_return_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_fp
+ && a_this->kind.import_rule);
+
+ str = cr_statement_import_rule_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_destroy:
+ *
+ * @a_this: the current instance of #CRStatement.
+ *
+ *Destructor of #CRStatement.
+ */
+void
+cr_statement_destroy (CRStatement * a_this)
+{
+ CRStatement *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ /*go get the tail of the list */
+ for (cur = a_this; cur && cur->next; cur = cur->next) {
+ cr_statement_clear (cur);
+ }
+
+ if (cur)
+ cr_statement_clear (cur);
+
+ if (cur->prev == NULL) {
+ g_free (a_this);
+ return;
+ }
+
+ /*walk backward and free next element */
+ for (cur = cur->prev; cur && cur->prev; cur = cur->prev) {
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+ }
+
+ if (!cur)
+ return;
+
+ /*free the one remaining list */
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+
+ g_free (cur);
+ cur = NULL;
+}
diff --git a/src/st/croco/cr-statement.h b/src/st/croco/cr-statement.h
new file mode 100644
index 0000000..c5bec97
--- /dev/null
+++ b/src/st/croco/cr-statement.h
@@ -0,0 +1,440 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <stdio.h>
+#include "cr-utils.h"
+#include "cr-term.h"
+#include "cr-selector.h"
+#include "cr-declaration.h"
+
+#ifndef __CR_STATEMENT_H__
+#define __CR_STATEMENT_H__
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *Declaration of the #CRStatement class.
+ */
+
+/*
+ *forward declaration of CRStyleSheet which is defined in
+ *cr-stylesheet.h
+ */
+
+struct _CRStatement ;
+
+/*
+ *typedef struct _CRStatement CRStatement ;
+ *this is forward declared in
+ *cr-declaration.h already.
+ */
+
+struct _CRAtMediaRule ;
+typedef struct _CRAtMediaRule CRAtMediaRule ;
+
+typedef struct _CRRuleSet CRRuleSet ;
+
+/**
+ *The abstraction of a css ruleset.
+ *A ruleset is made of a list of selectors,
+ *followed by a list of declarations.
+ */
+struct _CRRuleSet
+{
+ /**A list of instances of #CRSimpeSel*/
+ CRSelector *sel_list ;
+
+ /**A list of instances of #CRDeclaration*/
+ CRDeclaration *decl_list ;
+
+ /**
+ *The parent media rule, or NULL if
+ *no parent media rule exists.
+ */
+ CRStatement *parent_media_rule ;
+} ;
+
+/*
+ *a forward declaration of CRStylesheet.
+ *CRStylesheet is actually declared in
+ *cr-stylesheet.h
+ */
+struct _CRStyleSheet ;
+typedef struct _CRStyleSheet CRStyleSheet;
+
+
+/**The \@import rule abstraction.*/
+typedef struct _CRAtImportRule CRAtImportRule ;
+struct _CRAtImportRule
+{
+ /**the url of the import rule*/
+ CRString *url ;
+
+ GList *media_list ;
+
+ /**
+ *the stylesheet fetched from the url, if any.
+ *this is not "owned" by #CRAtImportRule which means
+ *it is not destroyed by the destructor of #CRAtImportRule.
+ */
+ CRStyleSheet * sheet;
+};
+
+
+/**abstraction of an \@media rule*/
+struct _CRAtMediaRule
+{
+ GList *media_list ;
+ CRStatement *rulesets ;
+} ;
+
+
+typedef struct _CRAtPageRule CRAtPageRule ;
+/**The \@page rule abstraction*/
+struct _CRAtPageRule
+{
+ /**a list of instances of #CRDeclaration*/
+ CRDeclaration *decl_list ;
+
+ /**page selector. Is a pseudo selector*/
+ CRString *name ;
+ CRString *pseudo ;
+} ;
+
+/**The \@charset rule abstraction*/
+typedef struct _CRAtCharsetRule CRAtCharsetRule ;
+struct _CRAtCharsetRule
+{
+ CRString * charset ;
+};
+
+/**The abstraction of the \@font-face rule.*/
+typedef struct _CRAtFontFaceRule CRAtFontFaceRule ;
+struct _CRAtFontFaceRule
+{
+ /*a list of instanaces of #CRDeclaration*/
+ CRDeclaration *decl_list ;
+} ;
+
+
+/**
+ *The possible types of css2 statements.
+ */
+enum CRStatementType
+{
+ /**
+ *A generic css at-rule
+ *each unknown at-rule will
+ *be of this type.
+ */
+
+ /**A css at-rule*/
+ AT_RULE_STMT = 0,
+
+ /*A css ruleset*/
+ RULESET_STMT,
+
+ /**A css2 import rule*/
+ AT_IMPORT_RULE_STMT,
+
+ /**A css2 media rule*/
+ AT_MEDIA_RULE_STMT,
+
+ /**A css2 page rule*/
+ AT_PAGE_RULE_STMT,
+
+ /**A css2 charset rule*/
+ AT_CHARSET_RULE_STMT,
+
+ /**A css2 font face rule*/
+ AT_FONT_FACE_RULE_STMT
+} ;
+
+
+/**
+ *The abstraction of css statement as defined
+ *in the chapter 4 and appendix D.1 of the css2 spec.
+ *A statement is actually a double chained list of
+ *statements.A statement can be a ruleset, an \@import
+ *rule, an \@page rule etc ...
+ */
+struct _CRStatement
+{
+ /**
+ *The type of the statement.
+ */
+ enum CRStatementType type ;
+
+ union
+ {
+ CRRuleSet *ruleset ;
+ CRAtImportRule *import_rule ;
+ CRAtMediaRule *media_rule ;
+ CRAtPageRule *page_rule ;
+ CRAtCharsetRule *charset_rule ;
+ CRAtFontFaceRule *font_face_rule ;
+ } kind ;
+
+ /*
+ *the specificity of the selector
+ *that matched this statement.
+ *This is only used by the cascading
+ *order determination algorithm.
+ */
+ gulong specificity ;
+
+ /*
+ *the style sheet that contains
+ *this css statement.
+ */
+ CRStyleSheet *parent_sheet ;
+ CRStatement *next ;
+ CRStatement *prev ;
+
+ CRParsingLocation location ;
+
+ /**
+ *a custom pointer useable by
+ *applications that use libcroco.
+ *libcroco itself will never modify
+ *this pointer.
+ */
+ gpointer app_data ;
+
+ /**
+ *a custom pointer used
+ *by the upper layers of libcroco.
+ *application should never use this
+ *pointer.
+ */
+ gpointer croco_data ;
+
+} ;
+
+
+gboolean
+cr_statement_does_buf_parses_against_core (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+CRStatement *
+cr_statement_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+CRStatement*
+cr_statement_new_ruleset (CRStyleSheet *a_sheet,
+ CRSelector *a_sel_list,
+ CRDeclaration *a_decl_list,
+ CRStatement *a_media_rule) ;
+CRStatement *
+cr_statement_ruleset_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_enc) ;
+
+CRStatement*
+cr_statement_new_at_import_rule (CRStyleSheet *a_container_sheet,
+ CRString *a_url,
+ GList *a_media_list,
+ CRStyleSheet *a_imported_sheet) ;
+
+CRStatement *
+cr_statement_at_import_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding) ;
+
+CRStatement *
+cr_statement_new_at_media_rule (CRStyleSheet *a_sheet,
+ CRStatement *a_ruleset,
+ GList *a_media) ;
+CRStatement *
+cr_statement_at_media_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_enc) ;
+
+CRStatement *
+cr_statement_new_at_charset_rule (CRStyleSheet *a_sheet,
+ CRString *a_charset) ;
+CRStatement *
+cr_statement_at_charset_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding);
+
+
+CRStatement *
+cr_statement_new_at_font_face_rule (CRStyleSheet *a_sheet,
+ CRDeclaration *a_font_decls) ;
+CRStatement *
+cr_statement_font_face_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+
+CRStatement *
+cr_statement_new_at_page_rule (CRStyleSheet *a_sheet,
+ CRDeclaration *a_decl_list,
+ CRString *a_name,
+ CRString *a_pseudo) ;
+CRStatement *
+cr_statement_at_page_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+
+enum CRStatus
+cr_statement_set_parent_sheet (CRStatement *a_this,
+ CRStyleSheet *a_sheet) ;
+
+enum CRStatus
+cr_statement_get_parent_sheet (CRStatement *a_this,
+ CRStyleSheet **a_sheet) ;
+
+CRStatement *
+cr_statement_append (CRStatement *a_this,
+ CRStatement *a_new) ;
+
+CRStatement*
+cr_statement_prepend (CRStatement *a_this,
+ CRStatement *a_new) ;
+
+CRStatement *
+cr_statement_unlink (CRStatement *a_stmt) ;
+
+enum CRStatus
+cr_statement_ruleset_set_sel_list (CRStatement *a_this,
+ CRSelector *a_sel_list) ;
+
+enum CRStatus
+cr_statement_ruleset_get_sel_list (CRStatement const *a_this,
+ CRSelector **a_list) ;
+
+enum CRStatus
+cr_statement_ruleset_set_decl_list (CRStatement *a_this,
+ CRDeclaration *a_list) ;
+
+enum CRStatus
+cr_statement_ruleset_get_declarations (CRStatement *a_this,
+ CRDeclaration **a_decl_list) ;
+
+enum CRStatus
+cr_statement_ruleset_append_decl2 (CRStatement *a_this,
+ CRString *a_prop, CRTerm *a_value) ;
+
+enum CRStatus
+cr_statement_ruleset_append_decl (CRStatement *a_this,
+ CRDeclaration *a_decl) ;
+
+enum CRStatus
+cr_statement_at_import_rule_set_imported_sheet (CRStatement *a_this,
+ CRStyleSheet *a_sheet) ;
+
+enum CRStatus
+cr_statement_at_import_rule_get_imported_sheet (CRStatement *a_this,
+ CRStyleSheet **a_sheet) ;
+
+enum CRStatus
+cr_statement_at_import_rule_set_url (CRStatement *a_this,
+ CRString *a_url) ;
+
+enum CRStatus
+cr_statement_at_import_rule_get_url (CRStatement const *a_this,
+ CRString **a_url) ;
+
+gint
+cr_statement_at_media_nr_rules (CRStatement const *a_this) ;
+
+CRStatement *
+cr_statement_at_media_get_from_list (CRStatement *a_this, int itemnr) ;
+
+enum CRStatus
+cr_statement_at_page_rule_set_sel (CRStatement *a_this,
+ CRSelector *a_sel) ;
+
+enum CRStatus
+cr_statement_at_page_rule_get_sel (CRStatement const *a_this,
+ CRSelector **a_sel) ;
+
+enum CRStatus
+cr_statement_at_page_rule_set_declarations (CRStatement *a_this,
+ CRDeclaration *a_decl_list) ;
+
+enum CRStatus
+cr_statement_at_page_rule_get_declarations (CRStatement *a_this,
+ CRDeclaration **a_decl_list) ;
+
+enum CRStatus
+cr_statement_at_charset_rule_set_charset (CRStatement *a_this,
+ CRString *a_charset) ;
+
+enum CRStatus
+cr_statement_at_charset_rule_get_charset (CRStatement const *a_this,
+ CRString **a_charset) ;
+
+enum CRStatus
+cr_statement_at_font_face_rule_set_decls (CRStatement *a_this,
+ CRDeclaration *a_decls) ;
+
+enum CRStatus
+cr_statement_at_font_face_rule_get_decls (CRStatement *a_this,
+ CRDeclaration **a_decls) ;
+
+enum CRStatus
+cr_statement_at_font_face_rule_add_decl (CRStatement *a_this,
+ CRString *a_prop,
+ CRTerm *a_value) ;
+
+gchar *
+cr_statement_to_string (CRStatement const * a_this, gulong a_indent) ;
+
+gchar*
+cr_statement_list_to_string (CRStatement const *a_this, gulong a_indent) ;
+
+void
+cr_statement_dump (CRStatement const *a_this, FILE *a_fp, gulong a_indent) ;
+
+void
+cr_statement_dump_ruleset (CRStatement const * a_this, FILE * a_fp,
+ glong a_indent) ;
+
+void
+cr_statement_dump_font_face_rule (CRStatement const * a_this,
+ FILE * a_fp,
+ glong a_indent) ;
+
+void
+cr_statement_dump_page (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent) ;
+
+
+void
+cr_statement_dump_media_rule (CRStatement const * a_this,
+ FILE * a_fp,
+ gulong a_indent) ;
+
+void
+cr_statement_dump_import_rule (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent) ;
+void
+cr_statement_dump_charset (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent) ;
+gint
+cr_statement_nr_rules (CRStatement const *a_this) ;
+
+CRStatement *
+cr_statement_get_from_list (CRStatement *a_this, int itemnr) ;
+
+void
+cr_statement_destroy (CRStatement *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_STATEMENT_H__*/
diff --git a/src/st/croco/cr-string.c b/src/st/croco/cr-string.c
new file mode 100644
index 0000000..6a16676
--- /dev/null
+++ b/src/st/croco/cr-string.c
@@ -0,0 +1,168 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-string.h"
+
+/**
+ *Instantiates a #CRString
+ *@return the newly instantiated #CRString
+ *Must be freed with cr_string_destroy().
+ */
+CRString *
+cr_string_new (void)
+{
+ CRString *result = NULL ;
+
+ result = g_try_malloc (sizeof (CRString)) ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ memset (result, 0, sizeof (CRString)) ;
+ result->stryng = g_string_new (NULL) ;
+ return result ;
+}
+
+/**
+ *Instantiate a string and initialise it to
+ *a_string.
+ *@param a_string the initial string
+ *@return the newly instantiated string.
+ */
+CRString *
+cr_string_new_from_string (const gchar * a_string)
+{
+ CRString *result = NULL ;
+
+ result = cr_string_new () ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ if (a_string)
+ g_string_append (result->stryng, a_string) ;
+ return result ;
+}
+
+/**
+ *Instantiates a #CRString from an instance of GString.
+ *@param a_string the input string that will be copied into
+ *the newly instantiated #CRString
+ *@return the newly instantiated #CRString.
+ */
+CRString *
+cr_string_new_from_gstring (GString const *a_string)
+{
+ CRString *result = NULL ;
+
+ result = cr_string_new () ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ if (a_string) {
+ g_string_append_len (result->stryng,
+ a_string->str,
+ a_string->len);
+
+ }
+ return result ;
+}
+
+CRString *
+cr_string_dup (CRString const *a_this)
+{
+ CRString *result = NULL ;
+ g_return_val_if_fail (a_this, NULL) ;
+
+ result = cr_string_new_from_gstring (a_this->stryng) ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ cr_parsing_location_copy (&result->location,
+ &a_this->location) ;
+ return result ;
+}
+
+gchar *
+cr_string_dup2 (CRString const *a_this)
+{
+ gchar *result = NULL ;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ if (a_this
+ && a_this->stryng
+ && a_this->stryng->str) {
+ result = g_strndup (a_this->stryng->str,
+ a_this->stryng->len) ;
+ }
+ return result ;
+}
+
+/**
+ *Returns a pointer to the internal raw NULL terminated string
+ *of the current instance of #CRString.
+ *@param a_this the current instance of #CRString
+ */
+const gchar *
+cr_string_peek_raw_str (CRString const *a_this)
+{
+ g_return_val_if_fail (a_this, NULL) ;
+
+ if (a_this->stryng && a_this->stryng->str)
+ return a_this->stryng->str ;
+ return NULL ;
+}
+
+/**
+ *Returns the length of the internal raw NULL terminated
+ *string of the current instance of #CRString.
+ *@param a_this the current instance of #CRString.
+ *@return the len of the internal raw NULL termninated string,
+ *of -1 if no length can be returned.
+ */
+gint
+cr_string_peek_raw_str_len (CRString const *a_this)
+{
+ g_return_val_if_fail (a_this && a_this->stryng,
+ -1) ;
+ return a_this->stryng->len ;
+}
+
+/**
+ *@param a_this the #CRString to destroy.
+ */
+void
+cr_string_destroy (CRString *a_this)
+{
+ g_return_if_fail (a_this) ;
+
+ if (a_this->stryng) {
+ g_string_free (a_this->stryng, TRUE) ;
+ a_this->stryng = NULL ;
+ }
+ g_free (a_this) ;
+}
diff --git a/src/st/croco/cr-string.h b/src/st/croco/cr-string.h
new file mode 100644
index 0000000..2700f0e
--- /dev/null
+++ b/src/st/croco/cr-string.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyright information.
+ */
+
+/**
+ *@file
+ *Declaration file of the #CRString class.
+ */
+
+#ifndef __CR_STRING_H__
+#define __CR_STRING_H__
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+typedef struct _CRString CRString ;
+
+/**
+ *This is a ship implementation of string based on GString.
+ *Actually, the aim of CRString is to store the parsing location
+ *(line,column,byte offset) at which a given string has been parsed
+ *in the input CSS.
+ *So this class has a gstring field of type GString that users can
+ *freely manipulate, and also a CRParginLocation type where the
+ *parsing location is store. If you don't want to deal with parsing
+ *location stuffs, then use GString instead. If we were in C++ for example,
+ *CRString would just inherit GString and just add accessors to
+ *the CRParsingLocation data ... but we are not and we still have
+ *to provide the parsing location information.
+ */
+struct _CRString {
+ /**
+ *The GString where all the string
+ *operation happen.
+ */
+ GString *stryng ;
+ /**
+ *The parsing location storage area.
+ */
+ CRParsingLocation location ;
+} ;
+
+CRString * cr_string_new (void) ;
+
+CRString *cr_string_new_from_string (const gchar * a_string) ;
+CRString * cr_string_new_from_gstring (GString const *a_string) ;
+CRString *cr_string_dup (CRString const *a_this) ;
+gchar *cr_string_dup2 (CRString const *a_this) ;
+const gchar *cr_string_peek_raw_str (CRString const *a_this) ;
+gint cr_string_peek_raw_str_len (CRString const *a_this) ;
+void cr_string_destroy (CRString *a_this) ;
+
+G_END_DECLS
+
+#endif
diff --git a/src/st/croco/cr-stylesheet.c b/src/st/croco/cr-stylesheet.c
new file mode 100644
index 0000000..63e763f
--- /dev/null
+++ b/src/st/croco/cr-stylesheet.c
@@ -0,0 +1,177 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2004 Dodji Seketeli
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include "string.h"
+#include "cr-stylesheet.h"
+
+/**
+ *@file
+ *The definition of the #CRStyleSheet class
+ */
+
+/**
+ *Constructor of the #CRStyleSheet class.
+ *@param the initial list of css statements.
+ *@return the newly built css2 stylesheet, or NULL in case of error.
+ */
+CRStyleSheet *
+cr_stylesheet_new (CRStatement * a_stmts)
+{
+ CRStyleSheet *result;
+
+ result = g_try_malloc (sizeof (CRStyleSheet));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStyleSheet));
+
+ if (a_stmts)
+ result->statements = a_stmts;
+
+ return result;
+}
+
+/**
+ *@param a_this the current instance of #CRStyleSheet
+ *@return the serialized stylesheet.
+ */
+gchar *
+cr_stylesheet_to_string (CRStyleSheet const *a_this)
+{
+ gchar *str = NULL;
+ GString *stringue = NULL;
+ CRStatement const *cur_stmt = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ if (a_this->statements) {
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ }
+ for (cur_stmt = a_this->statements;
+ cur_stmt; cur_stmt = cur_stmt->next) {
+ if (cur_stmt->prev) {
+ g_string_append (stringue, "\n\n") ;
+ }
+ str = cr_statement_to_string (cur_stmt, 0) ;
+ if (str) {
+ g_string_append (stringue, str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE) ;
+ stringue = NULL ;
+ }
+ return str ;
+}
+
+/**
+ *Dumps the current css2 stylesheet to a file.
+ *@param a_this the current instance of #CRStyleSheet.
+ *@param a_fp the destination file
+ */
+void
+cr_stylesheet_dump (CRStyleSheet const * a_this, FILE * a_fp)
+{
+ gchar *str = NULL ;
+
+ g_return_if_fail (a_this);
+
+ str = cr_stylesheet_to_string (a_this) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ *Return the number of rules in the stylesheet.
+ *@param a_this the current instance of #CRStyleSheet.
+ *@return number of rules in the stylesheet.
+ */
+gint
+cr_stylesheet_nr_rules (CRStyleSheet const * a_this)
+{
+ g_return_val_if_fail (a_this, -1);
+
+ return cr_statement_nr_rules (a_this->statements);
+}
+
+/**
+ *Use an index to get a CRStatement from the rules in a given stylesheet.
+ *@param a_this the current instance of #CRStatement.
+ *@param itemnr the index into the rules.
+ *@return CRStatement at position itemnr, if itemnr > number of rules - 1,
+ *it will return NULL.
+ */
+CRStatement *
+cr_stylesheet_statement_get_from_list (CRStyleSheet * a_this, int itemnr)
+{
+ g_return_val_if_fail (a_this, NULL);
+
+ return cr_statement_get_from_list (a_this->statements, itemnr);
+}
+
+void
+cr_stylesheet_ref (CRStyleSheet * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+gboolean
+cr_stylesheet_unref (CRStyleSheet * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count)
+ a_this->ref_count--;
+
+ if (!a_this->ref_count) {
+ cr_stylesheet_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ *Destructor of the #CRStyleSheet class.
+ *@param a_this the current instance of the #CRStyleSheet class.
+ */
+void
+cr_stylesheet_destroy (CRStyleSheet * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->statements) {
+ cr_statement_destroy (a_this->statements);
+ a_this->statements = NULL;
+ }
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-stylesheet.h b/src/st/croco/cr-stylesheet.h
new file mode 100644
index 0000000..2d6b4fa
--- /dev/null
+++ b/src/st/croco/cr-stylesheet.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * see COPYRIGHTS file for copyright information.
+ */
+
+
+#ifndef __CR_STYLESHEET_H__
+#define __CR_STYLESHEET_H__
+
+#include "cr-utils.h"
+#include "cr-statement.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the #CRStyleSheet class.
+ */
+
+
+enum CRStyleOrigin
+{
+ /*Please don't change the order of
+ *the values enumerated here ...
+ *New values should be added at the end,
+ *just before ORIGIN_END.
+ */
+ ORIGIN_UA = 0,
+ ORIGIN_USER,
+ ORIGIN_AUTHOR,
+
+ /*must always be the last one*/
+ NB_ORIGINS
+} ;
+
+/**
+ *An abstraction of a css stylesheet as defined
+ *by the css2 spec in chapter 4.
+ */
+struct _CRStyleSheet
+{
+ /**The css statements list*/
+ CRStatement *statements ;
+
+ enum CRStyleOrigin origin ;
+
+ /*the parent import rule, if any.*/
+ CRStatement *parent_import_rule ;
+
+ /**custom data used by libcroco*/
+ gpointer croco_data ;
+
+ /**
+ *custom application data pointer
+ *Can be used by applications.
+ */
+ gpointer app_data ;
+
+ /**
+ *the reference count of this instance
+ *Please, don't never ever modify it
+ *directly. Use cr_stylesheet_ref()
+ *and cr_stylesheet_unref() instead.
+ */
+ gulong ref_count ;
+} ;
+
+CRStyleSheet * cr_stylesheet_new (CRStatement *a_stmts) ;
+
+gchar * cr_stylesheet_to_string (CRStyleSheet const *a_this) ;
+void cr_stylesheet_dump (CRStyleSheet const *a_this, FILE *a_fp) ;
+
+gint cr_stylesheet_nr_rules (CRStyleSheet const *a_this) ;
+
+CRStatement * cr_stylesheet_statement_get_from_list (CRStyleSheet *a_this, int itemnr) ;
+
+void cr_stylesheet_ref (CRStyleSheet *a_this) ;
+
+gboolean cr_stylesheet_unref (CRStyleSheet *a_this) ;
+
+void cr_stylesheet_destroy (CRStyleSheet *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_STYLESHEET_H__*/
diff --git a/src/st/croco/cr-term.c b/src/st/croco/cr-term.c
new file mode 100644
index 0000000..b527d95
--- /dev/null
+++ b/src/st/croco/cr-term.c
@@ -0,0 +1,786 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "cr-term.h"
+#include "cr-num.h"
+#include "cr-parser.h"
+
+/**
+ *@file
+ *Definition of the #CRTem class.
+ */
+
+static void
+cr_term_clear (CRTerm * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case TERM_NUMBER:
+ if (a_this->content.num) {
+ cr_num_destroy (a_this->content.num);
+ a_this->content.num = NULL;
+ }
+ break;
+
+ case TERM_FUNCTION:
+ if (a_this->ext_content.func_param) {
+ cr_term_destroy (a_this->ext_content.func_param);
+ a_this->ext_content.func_param = NULL;
+ }
+ case TERM_STRING:
+ case TERM_IDENT:
+ case TERM_URI:
+ case TERM_HASH:
+ if (a_this->content.str) {
+ cr_string_destroy (a_this->content.str);
+ a_this->content.str = NULL;
+ }
+ break;
+
+ case TERM_RGB:
+ if (a_this->content.rgb) {
+ cr_rgb_destroy (a_this->content.rgb);
+ a_this->content.rgb = NULL;
+ }
+ break;
+
+ case TERM_UNICODERANGE:
+ case TERM_NO_TYPE:
+ default:
+ break;
+ }
+
+ a_this->type = TERM_NO_TYPE;
+}
+
+/**
+ *Instantiate a #CRTerm.
+ *@return the newly build instance
+ *of #CRTerm.
+ */
+CRTerm *
+cr_term_new (void)
+{
+ CRTerm *result = NULL;
+
+ result = g_try_malloc (sizeof (CRTerm));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRTerm));
+ return result;
+}
+
+/**
+ *Parses an expression as defined by the css2 spec
+ *and builds the expression as a list of terms.
+ *@param a_buf the buffer to parse.
+ *@return a pointer to the first term of the expression or
+ *NULL if parsing failed.
+ */
+CRTerm *
+cr_term_parse_expression_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ CRParser *parser = NULL;
+ CRTerm *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ g_return_val_if_fail (parser, NULL);
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK) {
+ goto cleanup;
+ }
+ status = cr_parser_parse_expr (parser, &result);
+ if (status != CR_OK) {
+ if (result) {
+ cr_term_destroy (result);
+ result = NULL;
+ }
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ return result;
+}
+
+enum CRStatus
+cr_term_set_number (CRTerm * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_NUMBER;
+ a_this->content.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_function (CRTerm * a_this, CRString * a_func_name,
+ CRTerm * a_func_param)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_FUNCTION;
+ a_this->content.str = a_func_name;
+ a_this->ext_content.func_param = a_func_param;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_string (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_STRING;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_ident (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_IDENT;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_uri (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_URI;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_rgb (CRTerm * a_this, CRRgb * a_rgb)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_RGB;
+ a_this->content.rgb = a_rgb;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_hash (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_HASH;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+/**
+ *Appends a new term to the current list of #CRTerm.
+ *
+ *@param a_this the "this pointer" of the current instance
+ *of #CRTerm .
+ *@param a_new_term the term to append.
+ *@return the list of terms with the a_new_term appended to it.
+ */
+CRTerm *
+cr_term_append_term (CRTerm * a_this, CRTerm * a_new_term)
+{
+ CRTerm *cur = NULL;
+
+ g_return_val_if_fail (a_new_term, NULL);
+
+ if (a_this == NULL)
+ return a_new_term;
+
+ for (cur = a_this; cur->next; cur = cur->next) ;
+
+ cur->next = a_new_term;
+ a_new_term->prev = cur;
+
+ return a_this;
+}
+
+/**
+ *Prepends a term to the list of terms represented by a_this.
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *#CRTerm .
+ *@param a_new_term the term to prepend.
+ *@return the head of the new list.
+ */
+CRTerm *
+cr_term_prepend_term (CRTerm * a_this, CRTerm * a_new_term)
+{
+ g_return_val_if_fail (a_this && a_new_term, NULL);
+
+ a_new_term->next = a_this;
+ a_this->prev = a_new_term;
+
+ return a_new_term;
+}
+
+/**
+ *Serializes the expression represented by
+ *the chained instances of #CRterm.
+ *@param a_this the current instance of #CRTerm
+ *@return the zero terminated string containing the serialized
+ *form of #CRTerm. MUST BE FREED BY THE CALLER using g_free().
+ */
+guchar *
+cr_term_to_string (CRTerm const * a_this)
+{
+ GString *str_buf = NULL;
+ CRTerm const *cur = NULL;
+ guchar *result = NULL,
+ *content = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if ((cur->content.str == NULL)
+ && (cur->content.num == NULL)
+ && (cur->content.rgb == NULL))
+ continue;
+
+ switch (cur->the_operator) {
+ case DIVIDE:
+ g_string_append (str_buf, " / ");
+ break;
+
+ case COMMA:
+ g_string_append (str_buf, ", ");
+ break;
+
+ case NO_OP:
+ if (cur->prev) {
+ g_string_append (str_buf, " ");
+ }
+ break;
+ default:
+
+ break;
+ }
+
+ switch (cur->unary_op) {
+ case PLUS_UOP:
+ g_string_append (str_buf, "+");
+ break;
+
+ case MINUS_UOP:
+ g_string_append (str_buf, "-");
+ break;
+
+ default:
+ break;
+ }
+
+ switch (cur->type) {
+ case TERM_NUMBER:
+ if (cur->content.num) {
+ content = cr_num_to_string (cur->content.num);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+
+ break;
+
+ case TERM_FUNCTION:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf, "%s(",
+ content);
+
+ if (cur->ext_content.func_param) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_term_to_string
+ (cur->
+ ext_content.func_param);
+
+ if (tmp_str) {
+ g_string_append (str_buf,
+ (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ g_string_append (str_buf, ")");
+ g_free (content);
+ content = NULL;
+ }
+
+ break;
+
+ case TERM_STRING:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "\"%s\"", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_IDENT:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_URI:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf
+ (str_buf, "url(%s)", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_RGB:
+ if (cur->content.rgb) {
+ guchar *tmp_str = NULL;
+
+ g_string_append (str_buf, "rgb(");
+ tmp_str = cr_rgb_to_string (cur->content.rgb);
+
+ if (tmp_str) {
+ g_string_append (str_buf, (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ g_string_append (str_buf, ")");
+ }
+
+ break;
+
+ case TERM_UNICODERANGE:
+ g_string_append
+ (str_buf,
+ "?found unicoderange: dump not supported yet?");
+ break;
+
+ case TERM_HASH:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "#%s", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ default:
+ g_string_append (str_buf,
+ "Unrecognized Term type");
+ break;
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+guchar *
+cr_term_one_to_string (CRTerm const * a_this)
+{
+ GString *str_buf = NULL;
+ guchar *result = NULL,
+ *content = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ if ((a_this->content.str == NULL)
+ && (a_this->content.num == NULL)
+ && (a_this->content.rgb == NULL))
+ return NULL ;
+
+ switch (a_this->the_operator) {
+ case DIVIDE:
+ g_string_append_printf (str_buf, " / ");
+ break;
+
+ case COMMA:
+ g_string_append_printf (str_buf, ", ");
+ break;
+
+ case NO_OP:
+ if (a_this->prev) {
+ g_string_append_printf (str_buf, " ");
+ }
+ break;
+ default:
+
+ break;
+ }
+
+ switch (a_this->unary_op) {
+ case PLUS_UOP:
+ g_string_append_printf (str_buf, "+");
+ break;
+
+ case MINUS_UOP:
+ g_string_append_printf (str_buf, "-");
+ break;
+
+ default:
+ break;
+ }
+
+ switch (a_this->type) {
+ case TERM_NUMBER:
+ if (a_this->content.num) {
+ content = cr_num_to_string (a_this->content.num);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+
+ break;
+
+ case TERM_FUNCTION:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf, "%s(",
+ content);
+
+ if (a_this->ext_content.func_param) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_term_to_string
+ (a_this->
+ ext_content.func_param);
+
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf,
+ "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+
+ g_string_append_printf (str_buf, ")");
+ g_free (content);
+ content = NULL;
+ }
+ }
+
+ break;
+
+ case TERM_STRING:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "\"%s\"", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_IDENT:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_URI:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf
+ (str_buf, "url(%s)", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_RGB:
+ if (a_this->content.rgb) {
+ guchar *tmp_str = NULL;
+
+ g_string_append_printf (str_buf, "rgb(");
+ tmp_str = cr_rgb_to_string (a_this->content.rgb);
+
+ if (tmp_str) {
+ g_string_append (str_buf, (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ g_string_append_printf (str_buf, ")");
+ }
+
+ break;
+
+ case TERM_UNICODERANGE:
+ g_string_append_printf
+ (str_buf,
+ "?found unicoderange: dump not supported yet?");
+ break;
+
+ case TERM_HASH:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "#%s", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ default:
+ g_string_append_printf (str_buf,
+ "%s",
+ "Unrecognized Term type");
+ break;
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ *Dumps the expression (a list of terms connected by operators)
+ *to a file.
+ *TODO: finish the dump. The dump of some type of terms have not yet been
+ *implemented.
+ *@param a_this the current instance of #CRTerm.
+ *@param a_fp the destination file pointer.
+ */
+void
+cr_term_dump (CRTerm const * a_this, FILE * a_fp)
+{
+ guchar *content = NULL;
+
+ g_return_if_fail (a_this);
+
+ content = cr_term_to_string (a_this);
+
+ if (content) {
+ fprintf (a_fp, "%s", content);
+ g_free (content);
+ }
+}
+
+/**
+ *Return the number of terms in the expression.
+ *@param a_this the current instance of #CRTerm.
+ *@return number of terms in the expression.
+ */
+int
+cr_term_nr_values (CRTerm const *a_this)
+{
+ CRTerm const *cur = NULL ;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, -1) ;
+
+ for (cur = a_this ; cur ; cur = cur->next)
+ nr ++;
+ return nr;
+}
+
+/**
+ *Use an index to get a CRTerm from the expression.
+ *@param a_this the current instance of #CRTerm.
+ *@param itemnr the index into the expression.
+ *@return CRTerm at position itemnr, if itemnr > number of terms - 1,
+ *it will return NULL.
+ */
+CRTerm *
+cr_term_get_from_list (CRTerm *a_this, int itemnr)
+{
+ CRTerm *cur = NULL ;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ for (cur = a_this ; cur ; cur = cur->next)
+ if (nr++ == itemnr)
+ return cur;
+ return NULL;
+}
+
+/**
+ *Increments the reference counter of the current instance
+ *of #CRTerm.*
+ *@param a_this the current instance of #CRTerm.
+ */
+void
+cr_term_ref (CRTerm * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ *Decrements the ref count of the current instance of
+ *#CRTerm. If the ref count reaches zero, the instance is
+ *destroyed.
+ *@param a_this the current instance of #CRTerm.
+ *@return TRUE if the current instance has been destroyed, FALSE otherwise.
+ */
+gboolean
+cr_term_unref (CRTerm * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_term_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ *The destructor of the the #CRTerm class.
+ *@param a_this the "this pointer" of the current instance
+ *of #CRTerm.
+ */
+void
+cr_term_destroy (CRTerm * a_this)
+{
+ g_return_if_fail (a_this);
+
+ cr_term_clear (a_this);
+
+ if (a_this->next) {
+ cr_term_destroy (a_this->next);
+ a_this->next = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ }
+
+}
diff --git a/src/st/croco/cr-term.h b/src/st/croco/cr-term.h
new file mode 100644
index 0000000..0f22dda
--- /dev/null
+++ b/src/st/croco/cr-term.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-rgb.h"
+#include "cr-num.h"
+#include "cr-string.h"
+
+#ifndef __CR_TERM_H__
+#define __CR_TERM_H__
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *Declaration of the #CRTem class.
+ */
+
+enum CRTermType
+{
+ TERM_NO_TYPE = 0,
+ TERM_NUMBER,
+ TERM_FUNCTION,
+ TERM_STRING,
+ TERM_IDENT,
+ TERM_URI,
+ TERM_RGB,
+ TERM_UNICODERANGE,
+ TERM_HASH
+} ;
+
+
+enum UnaryOperator
+{
+ NO_UNARY_UOP = 0,
+ PLUS_UOP,
+ MINUS_UOP,
+ EMPTY_UNARY_UOP
+} ;
+
+enum Operator
+{
+ NO_OP = 0,
+ DIVIDE,
+ COMMA
+} ;
+
+struct _CRTerm ;
+typedef struct _CRTerm CRTerm ;
+
+/**
+ *An abstraction of a css2 term as
+ *defined in the CSS2 spec in appendix D.1:
+ *term ::=
+ *[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S*
+ *| ANGLE S* | TIME S* | FREQ S* | function ]
+ * | STRING S* | IDENT S* | URI S* | RGB S*
+ *| UNICODERANGE S* | hexcolor
+ */
+struct _CRTerm
+{
+ /**
+ *The type of the term.
+ */
+ enum CRTermType type ;
+
+ /**
+ *The unary operator associated to
+ *the current term.
+ */
+ enum UnaryOperator unary_op ;
+
+ /**
+ *The operator associated to the current term.
+ */
+ enum Operator the_operator ;
+
+
+ /**
+ *The content of the term.
+ *Depending of the type of the term,
+ *this holds either a number, a percentage ...
+ */
+ union
+ {
+ CRNum *num ;
+ CRString * str ;
+ CRRgb * rgb ;
+ } content ;
+
+ /**
+ *If the term is of type UNICODERANGE,
+ *this field holds the upper bound of the range.
+ *if the term is of type FUNCTION, this holds
+ *an instance of CRTerm that represents
+ * the expression which is the argument of the function.
+ */
+ union
+ {
+ CRTerm *func_param ;
+ } ext_content ;
+
+ /**
+ *A spare pointer, just in case.
+ *Can be used by the application.
+ */
+ gpointer app_data ;
+
+ glong ref_count ;
+
+ /**
+ *A pointer to the next term,
+ *just in case this term is part of
+ *an expression.
+ */
+ CRTerm *next ;
+
+ /**
+ *A pointer to the previous
+ *term.
+ */
+ CRTerm *prev ;
+ CRParsingLocation location ;
+} ;
+
+CRTerm * cr_term_parse_expression_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+CRTerm * cr_term_new (void) ;
+
+enum CRStatus cr_term_set_number (CRTerm *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_term_set_function (CRTerm *a_this,
+ CRString *a_func_name,
+ CRTerm *a_func_param) ;
+
+enum CRStatus cr_term_set_string (CRTerm *a_this, CRString *a_str) ;
+
+enum CRStatus cr_term_set_ident (CRTerm *a_this, CRString *a_str) ;
+
+enum CRStatus cr_term_set_uri (CRTerm *a_this, CRString *a_str) ;
+
+enum CRStatus cr_term_set_rgb (CRTerm *a_this, CRRgb *a_rgb) ;
+
+enum CRStatus cr_term_set_hash (CRTerm *a_this, CRString *a_str) ;
+
+CRTerm * cr_term_append_term (CRTerm *a_this, CRTerm *a_new_term) ;
+
+CRTerm * cr_term_prepend_term (CRTerm *a_this, CRTerm *a_new_term) ;
+
+guchar * cr_term_to_string (CRTerm const *a_this) ;
+
+guchar * cr_term_one_to_string (CRTerm const * a_this) ;
+
+void cr_term_dump (CRTerm const *a_this, FILE *a_fp) ;
+
+int cr_term_nr_values (CRTerm const *a_this) ;
+
+CRTerm * cr_term_get_from_list (CRTerm *a_this, int itemnr) ;
+
+void cr_term_ref (CRTerm *a_this) ;
+
+gboolean cr_term_unref (CRTerm *a_this) ;
+
+void cr_term_destroy (CRTerm * a_term) ;
+
+G_END_DECLS
+
+#endif /*__CR_TERM_H__*/
diff --git a/src/st/croco/cr-tknzr.c b/src/st/croco/cr-tknzr.c
new file mode 100644
index 0000000..54f18f2
--- /dev/null
+++ b/src/st/croco/cr-tknzr.c
@@ -0,0 +1,2762 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See the COPYRIGHTS file for copyrights information.
+ */
+
+/**
+ *@file
+ *The definition of the #CRTknzr (tokenizer)
+ *class.
+ */
+
+#include "string.h"
+#include "cr-tknzr.h"
+#include "cr-doc-handler.h"
+
+struct _CRTknzrPriv {
+ /**The parser input stream of bytes*/
+ CRInput *input;
+
+ /**
+ *A cache where tknzr_unget_token()
+ *puts back the token. tknzr_get_next_token()
+ *first look in this cache, and if and
+ *only if it's empty, fetches the next token
+ *from the input stream.
+ */
+ CRToken *token_cache;
+
+ /**
+ *The position of the end of the previous token
+ *or char fetched.
+ */
+ CRInputPos prev_pos;
+
+ CRDocHandler *sac_handler;
+
+ /**
+ *The reference count of the current instance
+ *of #CRTknzr. Is manipulated by cr_tknzr_ref()
+ *and cr_tknzr_unref().
+ */
+ glong ref_count;
+};
+
+#define PRIVATE(obj) ((obj)->priv)
+
+/**
+ *return TRUE if the character is a number ([0-9]), FALSE otherwise
+ *@param a_char the char to test.
+ */
+#define IS_NUM(a_char) (((a_char) >= '0' && (a_char) <= '9')?TRUE:FALSE)
+
+/**
+ *Checks if 'status' equals CR_OK. If not, goto the 'error' label.
+ *
+ *@param status the status (of type enum CRStatus) to test.
+ *@param is_exception if set to FALSE, the final status returned the
+ *current function will be CR_PARSING_ERROR. If set to TRUE, the
+ *current status will be the current value of the 'status' variable.
+ *
+ */
+#define CHECK_PARSING_STATUS(status, is_exception) \
+if ((status) != CR_OK) \
+{ \
+ if (is_exception == FALSE) \
+ { \
+ status = CR_PARSING_ERROR ; \
+ } \
+ goto error ; \
+}
+
+/**
+ *Peeks the next char from the input stream of the current tokenizer.
+ *invokes CHECK_PARSING_STATUS on the status returned by
+ *cr_tknzr_input_peek_char().
+ *
+ *@param the current instance of #CRTkzr.
+ *@param to_char a pointer to the char where to store the
+ *char peeked.
+ */
+#define PEEK_NEXT_CHAR(a_tknzr, a_to_char) \
+{\
+status = cr_tknzr_peek_char (a_tknzr, a_to_char) ; \
+CHECK_PARSING_STATUS (status, TRUE) \
+}
+
+/**
+ *Reads the next char from the input stream of the current parser.
+ *In case of error, jumps to the "error:" label located in the
+ *function where this macro is called.
+ *@param parser the current instance of #CRTknzr
+ *@param to_char a pointer to the guint32 char where to store
+ *the character read.
+ */
+#define READ_NEXT_CHAR(a_tknzr, to_char) \
+status = cr_tknzr_read_char (a_tknzr, to_char) ;\
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Gets information about the current position in
+ *the input of the parser.
+ *In case of failure, this macro returns from the
+ *calling function and
+ *returns a status code of type enum #CRStatus.
+ *@param parser the current instance of #CRTknzr.
+ *@param pos out parameter. A pointer to the position
+ *inside the current parser input. Must
+ */
+#define RECORD_INITIAL_POS(a_tknzr, a_pos) \
+status = cr_input_get_cur_pos (PRIVATE \
+(a_tknzr)->input, a_pos) ; \
+g_return_val_if_fail (status == CR_OK, status)
+
+/**
+ *Gets the address of the current byte inside the
+ *parser input.
+ *@param parser the current instance of #CRTknzr.
+ *@param addr out parameter a pointer (guchar*)
+ *to where the address must be put.
+ */
+#define RECORD_CUR_BYTE_ADDR(a_tknzr, a_addr) \
+status = cr_input_get_cur_byte_addr \
+ (PRIVATE (a_tknzr)->input, a_addr) ; \
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Peeks a byte from the topmost parser input at
+ *a given offset from the current position.
+ *If it fails, goto the "error:" label.
+ *
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_offset the offset of the byte to peek, the
+ *current byte having the offset '0'.
+ *@param a_byte_ptr out parameter a pointer (guchar*) to
+ *where the peeked char is to be stored.
+ */
+#define PEEK_BYTE(a_tknzr, a_offset, a_byte_ptr) \
+status = cr_tknzr_peek_byte (a_tknzr, \
+ a_offset, \
+ a_byte_ptr) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+#define BYTE(a_input, a_n, a_eof) \
+cr_input_peek_byte2 (a_input, a_n, a_eof)
+
+/**
+ *Reads a byte from the topmost parser input
+ *steam.
+ *If it fails, goto the "error" label.
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_byte_ptr the guchar * where to put the read char.
+ */
+#define READ_NEXT_BYTE(a_tknzr, a_byte_ptr) \
+status = \
+cr_input_read_byte (PRIVATE (a_tknzr)->input, a_byte_ptr) ;\
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skips a given number of byte in the topmost
+ *parser input. Don't update line and column number.
+ *In case of error, jumps to the "error:" label
+ *of the surrounding function.
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_nb_bytes the number of bytes to skip.
+ */
+#define SKIP_BYTES(a_tknzr, a_nb_bytes) \
+status = cr_input_seek_index (PRIVATE (a_tknzr)->input, \
+ CR_SEEK_CUR, a_nb_bytes) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skip utf8 encoded characters.
+ *Updates line and column numbers.
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_nb_chars the number of chars to skip. Must be of
+ *type glong.
+ */
+#define SKIP_CHARS(a_tknzr, a_nb_chars) \
+{ \
+gulong nb_chars = a_nb_chars ; \
+status = cr_input_consume_chars \
+ (PRIVATE (a_tknzr)->input,0, &nb_chars) ; \
+CHECK_PARSING_STATUS (status, TRUE) ; \
+}
+
+/**
+ *Tests the condition and if it is false, sets
+ *status to "CR_PARSING_ERROR" and goto the 'error'
+ *label.
+ *@param condition the condition to test.
+ */
+#define ENSURE_PARSING_COND(condition) \
+if (! (condition)) {status = CR_PARSING_ERROR; goto error ;}
+
+static enum CRStatus cr_tknzr_parse_nl (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location);
+
+static enum CRStatus cr_tknzr_parse_w (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location) ;
+
+static enum CRStatus cr_tknzr_parse_unicode_escape (CRTknzr * a_this,
+ guint32 * a_unicode,
+ CRParsingLocation *a_location) ;
+
+static enum CRStatus cr_tknzr_parse_escape (CRTknzr * a_this,
+ guint32 * a_esc_code,
+ CRParsingLocation *a_location);
+
+static enum CRStatus cr_tknzr_parse_string (CRTknzr * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_tknzr_parse_comment (CRTknzr * a_this,
+ CRString ** a_comment);
+
+static enum CRStatus cr_tknzr_parse_nmstart (CRTknzr * a_this,
+ guint32 * a_char,
+ CRParsingLocation *a_location);
+
+static enum CRStatus cr_tknzr_parse_num (CRTknzr * a_this,
+ CRNum ** a_num);
+
+/**********************************
+ *PRIVATE methods
+ **********************************/
+
+/**
+ *Parses a "w" as defined by the css spec at [4.1.1]:
+ * w ::= [ \t\r\n\f]*
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out param. Upon successful completion, points
+ *to the beginning of the parsed white space, points to NULL otherwise.
+ *Can also point to NULL is there is no white space actually.
+ *@param a_end out param. Upon successful completion, points
+ *to the end of the parsed white space, points to NULL otherwise.
+ *Can also point to NULL is there is no white space actually.
+ */
+static enum CRStatus
+cr_tknzr_parse_w (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_start && a_end,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ *a_start = NULL;
+ *a_end = NULL;
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cr_utils_is_white_space (cur_char) == FALSE) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ RECORD_CUR_BYTE_ADDR (a_this, a_start);
+ *a_end = *a_start;
+
+ for (;;) {
+ gboolean is_eof = FALSE;
+
+ cr_input_get_end_of_file (PRIVATE (a_this)->input, &is_eof);
+ if (is_eof)
+ break;
+
+ status = cr_tknzr_peek_char (a_this, &cur_char);
+ if (status == CR_END_OF_INPUT_ERROR) {
+ break;
+ } else if (status != CR_OK) {
+ goto error;
+ }
+
+ if (cr_utils_is_white_space (cur_char) == TRUE) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ RECORD_CUR_BYTE_ADDR (a_this, a_end);
+ } else {
+ break;
+ }
+ }
+
+ return CR_OK;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a newline as defined in the css2 spec:
+ * nl ::= \n|\r\n|\r|\f
+ *
+ *@param a_this the "this pointer" of the current instance of #CRTknzr.
+ *@param a_start a pointer to the first character of the successfully
+ *parsed string.
+ *@param a_end a pointer to the last character of the successfully parsed
+ *string.
+ *@result CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_nl (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location)
+{
+ CRInputPos init_pos;
+ guchar next_chars[2] = { 0 };
+ enum CRStatus status = CR_PARSING_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_start && a_end, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &next_chars[0]);
+ PEEK_BYTE (a_this, 2, &next_chars[1]);
+
+ if ((next_chars[0] == '\r' && next_chars[1] == '\n')) {
+ SKIP_BYTES (a_this, 1);
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ SKIP_CHARS (a_this, 1);
+
+ RECORD_CUR_BYTE_ADDR (a_this, a_end);
+
+ status = CR_OK;
+ } else if (next_chars[0] == '\n'
+ || next_chars[0] == '\r' || next_chars[0] == '\f') {
+ SKIP_CHARS (a_this, 1);
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ RECORD_CUR_BYTE_ADDR (a_this, a_start);
+ *a_end = *a_start;
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ return CR_OK ;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos) ;
+ return status;
+}
+
+/**
+ *Go ahead in the parser input, skipping all the spaces.
+ *If the next char if not a white space, this function does nothing.
+ *In any cases, it stops when it encounters a non white space character.
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_try_to_skip_spaces (CRTknzr * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+ guint32 cur_char = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ status = cr_input_peek_char (PRIVATE (a_this)->input, &cur_char);
+
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR)
+ return CR_OK;
+ return status;
+ }
+
+ if (cr_utils_is_white_space (cur_char) == TRUE) {
+ gulong nb_chars = -1; /*consume all spaces */
+
+ status = cr_input_consume_white_spaces
+ (PRIVATE (a_this)->input, &nb_chars);
+ }
+
+ return status;
+}
+
+/**
+ *Parses a "comment" as defined in the css spec at [4.1.1]:
+ *COMMENT ::= \/\*[^*]*\*+([^/][^*]*\*+)*\/ .
+ *This complex regexp is just to say that comments start
+ *with the two chars '/''*' and ends with the two chars '*''/'.
+ *It also means that comments cannot be nested.
+ *So based on that, I've just tried to implement the parsing function
+ *simply and in a straight forward manner.
+ */
+static enum CRStatus
+cr_tknzr_parse_comment (CRTknzr * a_this,
+ CRString ** a_comment)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0, next_char= 0;
+ CRString *comment = NULL;
+ CRParsingLocation loc = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char) ;
+ ENSURE_PARSING_COND (cur_char == '/');
+ cr_tknzr_get_parsing_location (a_this, &loc) ;
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '*');
+ comment = cr_string_new ();
+ for (;;) { /* [^*]* */
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == '*')
+ break;
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+ }
+ /* Stop condition: next_char == '*' */
+ for (;;) { /* \*+ */
+ READ_NEXT_CHAR(a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '*');
+ g_string_append_unichar (comment->stryng, cur_char);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char != '*')
+ break;
+ }
+ /* Stop condition: next_char != '*' */
+ for (;;) { /* ([^/][^*]*\*+)* */
+ if (next_char == '/')
+ break;
+ READ_NEXT_CHAR(a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+ for (;;) { /* [^*]* */
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == '*')
+ break;
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+ }
+ /* Stop condition: next_char = '*', no need to verify, because peek and read exit to error anyway */
+ for (;;) { /* \*+ */
+ READ_NEXT_CHAR(a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '*');
+ g_string_append_unichar (comment->stryng, cur_char);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char != '*')
+ break;
+ }
+ /* Continue condition: next_char != '*' */
+ }
+ /* Stop condition: next_char == '\/' */
+ READ_NEXT_CHAR(a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+
+ if (status == CR_OK) {
+ cr_parsing_location_copy (&comment->location,
+ &loc) ;
+ *a_comment = comment;
+ return CR_OK;
+ }
+ error:
+
+ if (comment) {
+ cr_string_destroy (comment);
+ comment = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses an 'unicode' escape sequence defined
+ *in css spec at chap 4.1.1:
+ *unicode ::= \\[0-9a-f]{1,6}[ \n\r\t\f]?
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out parameter. A pointer to the start
+ *of the unicode escape sequence. Must *NOT* be deleted by
+ *the caller.
+ *@param a_end out parameter. A pointer to the last character
+ *of the unicode escape sequence. Must *NOT* be deleted by the caller.
+ *@return CR_OK if parsing succeeded, an error code otherwise.
+ *Error code can be either CR_PARSING_ERROR if the string
+ *parsed just doesn't
+ *respect the production or another error if a
+ *lower level error occurred.
+ */
+static enum CRStatus
+cr_tknzr_parse_unicode_escape (CRTknzr * a_this,
+ guint32 * a_unicode,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char;
+ CRInputPos init_pos;
+ glong occur = 0;
+ guint32 unicode = 0;
+ guchar *tmp_char_ptr1 = NULL,
+ *tmp_char_ptr2 = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_unicode, CR_BAD_PARAM_ERROR);
+
+ /*first, let's backup the current position pointer */
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != '\\') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ PEEK_NEXT_CHAR (a_this, &cur_char);
+
+ for (occur = 0, unicode = 0; ((cur_char >= '0' && cur_char <= '9')
+ || (cur_char >= 'a' && cur_char <= 'f')
+ || (cur_char >= 'A' && cur_char <= 'F'))
+ && occur < 6; occur++) {
+ gint cur_char_val = 0;
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if ((cur_char >= '0' && cur_char <= '9')) {
+ cur_char_val = (cur_char - '0');
+ } else if ((cur_char >= 'a' && cur_char <= 'f')) {
+ cur_char_val = 10 + (cur_char - 'a');
+ } else if ((cur_char >= 'A' && cur_char <= 'F')) {
+ cur_char_val = 10 + (cur_char - 'A');
+ }
+
+ unicode = unicode * 16 + cur_char_val;
+
+ PEEK_NEXT_CHAR (a_this, &cur_char);
+ }
+
+ /* Eat a whitespace if possible. */
+ cr_tknzr_parse_w (a_this, &tmp_char_ptr1,
+ &tmp_char_ptr2, NULL);
+ *a_unicode = unicode;
+ return CR_OK;
+
+ error:
+ /*
+ *restore the initial position pointer backuped at
+ *the beginning of this function.
+ */
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *parses an escape sequence as defined by the css spec:
+ *escape ::= {unicode}|\\[ -~\200-\4177777]
+ *@param a_this the current instance of #CRTknzr .
+ */
+static enum CRStatus
+cr_tknzr_parse_escape (CRTknzr * a_this, guint32 * a_esc_code,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ guchar next_chars[2];
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_esc_code, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &next_chars[0]);
+ PEEK_BYTE (a_this, 2, &next_chars[1]);
+
+ if (next_chars[0] != '\\') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if ((next_chars[1] >= '0' && next_chars[1] <= '9')
+ || (next_chars[1] >= 'a' && next_chars[1] <= 'f')
+ || (next_chars[1] >= 'A' && next_chars[1] <= 'F')) {
+ status = cr_tknzr_parse_unicode_escape (a_this, a_esc_code,
+ a_location);
+ } else {
+ /*consume the '\' char */
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ /*then read the char after the '\' */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != ' ' && (cur_char < 200 || cur_char > 4177777)) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ *a_esc_code = cur_char;
+
+ }
+ if (status == CR_OK) {
+ return CR_OK;
+ }
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses a string type as defined in css spec [4.1.1]:
+ *
+ *string ::= {string1}|{string2}
+ *string1 ::= \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\"
+ *string2 ::= \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\'
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out parameter. Upon successful completion,
+ *points to the beginning of the string, points to an undefined value
+ *otherwise.
+ *@param a_end out parameter. Upon successful completion, points to
+ *the beginning of the string, points to an undefined value otherwise.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_string (CRTknzr * a_this, CRString ** a_str)
+{
+ guint32 cur_char = 0,
+ delim = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ CRString *str = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char == '"')
+ delim = '"';
+ else if (cur_char == '\'')
+ delim = '\'';
+ else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ str = cr_string_new ();
+ if (str) {
+ cr_tknzr_get_parsing_location
+ (a_this, &str->location) ;
+ }
+ for (;;) {
+ guchar next_chars[2] = { 0 };
+
+ PEEK_BYTE (a_this, 1, &next_chars[0]);
+ PEEK_BYTE (a_this, 2, &next_chars[1]);
+
+ if (next_chars[0] == '\\') {
+ guchar *tmp_char_ptr1 = NULL,
+ *tmp_char_ptr2 = NULL;
+ guint32 esc_code = 0;
+
+ if (next_chars[1] == '\'' || next_chars[1] == '"') {
+ g_string_append_unichar (str->stryng,
+ next_chars[1]);
+ SKIP_BYTES (a_this, 2);
+ status = CR_OK;
+ } else {
+ status = cr_tknzr_parse_escape
+ (a_this, &esc_code, NULL);
+
+ if (status == CR_OK) {
+ g_string_append_unichar
+ (str->stryng,
+ esc_code);
+ }
+ }
+
+ if (status != CR_OK) {
+ /*
+ *consume the '\' char, and try to parse
+ *a newline.
+ */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ status = cr_tknzr_parse_nl
+ (a_this, &tmp_char_ptr1,
+ &tmp_char_ptr2, NULL);
+ }
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else if (strchr ("\t !#$%&", next_chars[0])
+ || (next_chars[0] >= '(' && next_chars[0] <= '~')) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (str->stryng,
+ cur_char);
+ status = CR_OK;
+ }
+
+ else if (cr_utils_is_nonascii (next_chars[0])) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (str->stryng, cur_char);
+ } else if (next_chars[0] == delim) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ break;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ }
+
+ if (status == CR_OK) {
+ if (*a_str == NULL) {
+ *a_str = str;
+ str = NULL;
+ } else {
+ (*a_str)->stryng = g_string_append_len
+ ((*a_str)->stryng,
+ str->stryng->str,
+ str->stryng->len);
+ cr_string_destroy (str);
+ }
+ return CR_OK;
+ }
+
+ error:
+
+ if (str) {
+ cr_string_destroy (str) ;
+ str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses the an nmstart as defined by the css2 spec [4.1.1]:
+ * nmstart [a-zA-Z]|{nonascii}|{escape}
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out param. A pointer to the starting point of
+ *the token.
+ *@param a_end out param. A pointer to the ending point of the
+ *token.
+ *@param a_char out param. The actual parsed nmchar.
+ *@return CR_OK upon successful completion,
+ *an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_nmstart (CRTknzr * a_this,
+ guint32 * a_char,
+ CRParsingLocation *a_location)
+{
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ guint32 cur_char = 0,
+ next_char = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == '\\') {
+ status = cr_tknzr_parse_escape (a_this, a_char,
+ a_location);
+
+ if (status != CR_OK)
+ goto error;
+
+ } else if (cr_utils_is_nonascii (next_char) == TRUE
+ || ((next_char >= 'a') && (next_char <= 'z'))
+ || ((next_char >= 'A') && (next_char <= 'Z'))
+ ) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ *a_char = cur_char;
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ return CR_OK;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+
+}
+
+/**
+ *Parses an nmchar as described in the css spec at
+ *chap 4.1.1:
+ *nmchar ::= [a-z0-9-]|{nonascii}|{escape}
+ *
+ *Humm, I have added the possibility for nmchar to
+ *contain upper case letters.
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out param. A pointer to the starting point of
+ *the token.
+ *@param a_end out param. A pointer to the ending point of the
+ *token.
+ *@param a_char out param. The actual parsed nmchar.
+ *@return CR_OK upon successful completion,
+ *an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_nmchar (CRTknzr * a_this, guint32 * a_char,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char = 0,
+ next_char = 0;
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_input_peek_char (PRIVATE (a_this)->input,
+ &next_char) ;
+ if (status != CR_OK)
+ goto error;
+
+ if (next_char == '\\') {
+ status = cr_tknzr_parse_escape (a_this, a_char,
+ a_location);
+
+ if (status != CR_OK)
+ goto error;
+
+ } else if (cr_utils_is_nonascii (next_char) == TRUE
+ || ((next_char >= 'a') && (next_char <= 'z'))
+ || ((next_char >= 'A') && (next_char <= 'Z'))
+ || ((next_char >= '0') && (next_char <= '9'))
+ || (next_char == '-')
+ || (next_char == '_') /*'_' not allowed by the spec. */
+ ) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ *a_char = cur_char;
+ status = CR_OK;
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ return CR_OK;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses an "ident" as defined in css spec [4.1.1]:
+ *ident ::= {nmstart}{nmchar}*
+ *
+ *Actually parses it using the css3 grammar:
+ *ident ::= -?{nmstart}{nmchar}*
+ *@param a_this the currens instance of #CRTknzr.
+ *
+ *@param a_str a pointer to parsed ident. If *a_str is NULL,
+ *this function allocates a new instance of CRString. If not,
+ *the function just appends the parsed string to the one passed.
+ *In both cases it is up to the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_ident (CRTknzr * a_this, CRString ** a_str)
+{
+ guint32 tmp_char = 0;
+ CRString *stringue = NULL ;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ gboolean location_is_set = FALSE ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ PEEK_NEXT_CHAR (a_this, &tmp_char) ;
+ stringue = cr_string_new () ;
+ g_return_val_if_fail (stringue,
+ CR_OUT_OF_MEMORY_ERROR) ;
+
+ if (tmp_char == '-') {
+ READ_NEXT_CHAR (a_this, &tmp_char) ;
+ cr_tknzr_get_parsing_location
+ (a_this, &stringue->location) ;
+ location_is_set = TRUE ;
+ g_string_append_unichar (stringue->stryng,
+ tmp_char) ;
+ }
+ status = cr_tknzr_parse_nmstart (a_this, &tmp_char, NULL);
+ if (status != CR_OK) {
+ status = CR_PARSING_ERROR;
+ goto end ;
+ }
+ if (location_is_set == FALSE) {
+ cr_tknzr_get_parsing_location
+ (a_this, &stringue->location) ;
+ location_is_set = TRUE ;
+ }
+ g_string_append_unichar (stringue->stryng, tmp_char);
+ for (;;) {
+ status = cr_tknzr_parse_nmchar (a_this,
+ &tmp_char,
+ NULL);
+ if (status != CR_OK) {
+ status = CR_OK ;
+ break;
+ }
+ g_string_append_unichar (stringue->stryng, tmp_char);
+ }
+ if (status == CR_OK) {
+ if (!*a_str) {
+ *a_str = stringue ;
+
+ } else {
+ g_string_append_len ((*a_str)->stryng,
+ stringue->stryng->str,
+ stringue->stryng->len) ;
+ cr_string_destroy (stringue) ;
+ }
+ stringue = NULL ;
+ }
+
+ error:
+ end:
+ if (stringue) {
+ cr_string_destroy (stringue) ;
+ stringue = NULL ;
+ }
+ if (status != CR_OK ) {
+ cr_tknzr_set_cur_pos (a_this, &init_pos) ;
+ }
+ return status ;
+}
+
+
+/**
+ *Parses a "name" as defined by css spec [4.1.1]:
+ *name ::= {nmchar}+
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *
+ *@param a_str out parameter. A pointer to the successfully parsed
+ *name. If *a_str is set to NULL, this function allocates a new instance
+ *of CRString. If not, it just appends the parsed name to the passed *a_str.
+ *In both cases, it is up to the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_name (CRTknzr * a_this,
+ CRString ** a_str)
+{
+ guint32 tmp_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ gboolean str_needs_free = FALSE,
+ is_first_nmchar=TRUE ;
+ glong i = 0;
+ CRParsingLocation loc = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str,
+ CR_BAD_PARAM_ERROR) ;
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ if (*a_str == NULL) {
+ *a_str = cr_string_new ();
+ str_needs_free = TRUE;
+ }
+ for (i = 0;; i++) {
+ if (is_first_nmchar == TRUE) {
+ status = cr_tknzr_parse_nmchar
+ (a_this, &tmp_char,
+ &loc) ;
+ is_first_nmchar = FALSE ;
+ } else {
+ status = cr_tknzr_parse_nmchar
+ (a_this, &tmp_char, NULL) ;
+ }
+ if (status != CR_OK)
+ break;
+ g_string_append_unichar ((*a_str)->stryng,
+ tmp_char);
+ }
+ if (i > 0) {
+ cr_parsing_location_copy
+ (&(*a_str)->location, &loc) ;
+ return CR_OK;
+ }
+ if (str_needs_free == TRUE && *a_str) {
+ cr_string_destroy (*a_str);
+ *a_str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return CR_PARSING_ERROR;
+}
+
+/**
+ *Parses a "hash" as defined by the css spec in [4.1.1]:
+ *HASH ::= #{name}
+ */
+static enum CRStatus
+cr_tknzr_parse_hash (CRTknzr * a_this, CRString ** a_str)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ gboolean str_needs_free = FALSE;
+ CRParsingLocation loc = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (cur_char != '#') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (*a_str == NULL) {
+ *a_str = cr_string_new ();
+ str_needs_free = TRUE;
+ }
+ cr_tknzr_get_parsing_location (a_this,
+ &loc) ;
+ status = cr_tknzr_parse_name (a_this, a_str);
+ cr_parsing_location_copy (&(*a_str)->location, &loc) ;
+ if (status != CR_OK) {
+ goto error;
+ }
+ return CR_OK;
+
+ error:
+ if (str_needs_free == TRUE && *a_str) {
+ cr_string_destroy (*a_str);
+ *a_str = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses an uri as defined by the css spec [4.1.1]:
+ * URI ::= url\({w}{string}{w}\)
+ * |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\)
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_str the successfully parsed url.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_uri (CRTknzr * a_this,
+ CRString ** a_str)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_PARSING_ERROR;
+ guchar tab[4] = { 0 }, *tmp_ptr1 = NULL, *tmp_ptr2 = NULL;
+ CRString *str = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &tab[0]);
+ PEEK_BYTE (a_this, 2, &tab[1]);
+ PEEK_BYTE (a_this, 3, &tab[2]);
+ PEEK_BYTE (a_this, 4, &tab[3]);
+
+ if (tab[0] != 'u' || tab[1] != 'r' || tab[2] != 'l' || tab[3] != '(') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ /*
+ *Here, we want to skip 4 bytes ('u''r''l''(').
+ *But we also need to keep track of the parsing location
+ *of the 'u'. So, we skip 1 byte, we record the parsing
+ *location, then we skip the 3 remaining bytes.
+ */
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this, &location) ;
+ SKIP_CHARS (a_this, 3);
+ cr_tknzr_try_to_skip_spaces (a_this);
+ status = cr_tknzr_parse_string (a_this, a_str);
+
+ if (status == CR_OK) {
+ guint32 next_char = 0;
+ status = cr_tknzr_parse_w (a_this, &tmp_ptr1,
+ &tmp_ptr2, NULL);
+ cr_tknzr_try_to_skip_spaces (a_this);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == ')') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+ }
+ if (status != CR_OK) {
+ str = cr_string_new ();
+ for (;;) {
+ guint32 next_char = 0;
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (strchr ("!#$%&", next_char)
+ || (next_char >= '*' && next_char <= '~')
+ || (cr_utils_is_nonascii (next_char) == TRUE)) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar
+ (str->stryng, cur_char);
+ status = CR_OK;
+ } else {
+ guint32 esc_code = 0;
+ status = cr_tknzr_parse_escape
+ (a_this, &esc_code, NULL);
+ if (status == CR_OK) {
+ g_string_append_unichar
+ (str->stryng,
+ esc_code);
+ } else {
+ status = CR_OK;
+ break;
+ }
+ }
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (cur_char == ')') {
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (str) {
+ if (*a_str == NULL) {
+ *a_str = str;
+ str = NULL;
+ } else {
+ g_string_append_len
+ ((*a_str)->stryng,
+ str->stryng->str,
+ str->stryng->len);
+ cr_string_destroy (str);
+ }
+ }
+ }
+
+ cr_parsing_location_copy
+ (&(*a_str)->location,
+ &location) ;
+ return CR_OK ;
+ error:
+ if (str) {
+ cr_string_destroy (str);
+ str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *parses an RGB as defined in the css2 spec.
+ *rgb: rgb '('S*{num}%?S* ',' {num}#?S*,S*{num}#?S*')'
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *@param a_rgb out parameter the parsed rgb.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_rgb (CRTknzr * a_this, CRRgb ** a_rgb)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRNum *num = NULL;
+ guchar next_bytes[3] = { 0 }, cur_byte = 0;
+ glong red = 0,
+ green = 0,
+ blue = 0,
+ i = 0;
+ gboolean is_percentage = FALSE;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &next_bytes[0]);
+ PEEK_BYTE (a_this, 2, &next_bytes[1]);
+ PEEK_BYTE (a_this, 3, &next_bytes[2]);
+
+ if (((next_bytes[0] == 'r') || (next_bytes[0] == 'R'))
+ && ((next_bytes[1] == 'g') || (next_bytes[1] == 'G'))
+ && ((next_bytes[2] == 'b') || (next_bytes[2] == 'B'))) {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this, &location) ;
+ SKIP_CHARS (a_this, 2);
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ READ_NEXT_BYTE (a_this, &cur_byte);
+ ENSURE_PARSING_COND (cur_byte == '(');
+
+ cr_tknzr_try_to_skip_spaces (a_this);
+ status = cr_tknzr_parse_num (a_this, &num);
+ ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL));
+
+ if (num->val > G_MAXLONG) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ red = num->val;
+ cr_num_destroy (num);
+ num = NULL;
+
+ PEEK_BYTE (a_this, 1, &next_bytes[0]);
+ if (next_bytes[0] == '%') {
+ SKIP_CHARS (a_this, 1);
+ is_percentage = TRUE;
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+
+ for (i = 0; i < 2; i++) {
+ READ_NEXT_BYTE (a_this, &cur_byte);
+ ENSURE_PARSING_COND (cur_byte == ',');
+
+ cr_tknzr_try_to_skip_spaces (a_this);
+ status = cr_tknzr_parse_num (a_this, &num);
+ ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL));
+
+ if (num->val > G_MAXLONG) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ PEEK_BYTE (a_this, 1, &next_bytes[0]);
+ if (next_bytes[0] == '%') {
+ SKIP_CHARS (a_this, 1);
+ is_percentage = 1;
+ }
+
+ if (i == 0) {
+ green = num->val;
+ } else if (i == 1) {
+ blue = num->val;
+ }
+
+ if (num) {
+ cr_num_destroy (num);
+ num = NULL;
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+ }
+
+ READ_NEXT_BYTE (a_this, &cur_byte);
+ if (*a_rgb == NULL) {
+ *a_rgb = cr_rgb_new_with_vals (red, green, blue,
+ is_percentage);
+
+ if (*a_rgb == NULL) {
+ status = CR_ERROR;
+ goto error;
+ }
+ status = CR_OK;
+ } else {
+ (*a_rgb)->red = red;
+ (*a_rgb)->green = green;
+ (*a_rgb)->blue = blue;
+ (*a_rgb)->is_percentage = is_percentage;
+
+ status = CR_OK;
+ }
+
+ if (status == CR_OK) {
+ if (a_rgb && *a_rgb) {
+ cr_parsing_location_copy
+ (&(*a_rgb)->location,
+ &location) ;
+ }
+ return CR_OK;
+ }
+
+ error:
+ if (num) {
+ cr_num_destroy (num);
+ num = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return CR_OK;
+}
+
+/**
+ *Parses a atkeyword as defined by the css spec in [4.1.1]:
+ *ATKEYWORD ::= @{ident}
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *#CRTknzr.
+ *
+ *@param a_str out parameter. The parsed atkeyword. If *a_str is
+ *set to NULL this function allocates a new instance of CRString and
+ *sets it to the parsed atkeyword. If not, this function just appends
+ *the parsed atkeyword to the end of *a_str. In both cases it is up to
+ *the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_atkeyword (CRTknzr * a_this,
+ CRString ** a_str)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ gboolean str_needs_free = FALSE;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != '@') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if (*a_str == NULL) {
+ *a_str = cr_string_new ();
+ str_needs_free = TRUE;
+ }
+ status = cr_tknzr_parse_ident (a_this, a_str);
+ if (status != CR_OK) {
+ goto error;
+ }
+ return CR_OK;
+ error:
+
+ if (str_needs_free == TRUE && *a_str) {
+ cr_string_destroy (*a_str);
+ *a_str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+static enum CRStatus
+cr_tknzr_parse_important (CRTknzr * a_this,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '!');
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+
+ if (BYTE (PRIVATE (a_this)->input, 1, NULL) == 'i'
+ && BYTE (PRIVATE (a_this)->input, 2, NULL) == 'm'
+ && BYTE (PRIVATE (a_this)->input, 3, NULL) == 'p'
+ && BYTE (PRIVATE (a_this)->input, 4, NULL) == 'o'
+ && BYTE (PRIVATE (a_this)->input, 5, NULL) == 'r'
+ && BYTE (PRIVATE (a_this)->input, 6, NULL) == 't'
+ && BYTE (PRIVATE (a_this)->input, 7, NULL) == 'a'
+ && BYTE (PRIVATE (a_this)->input, 8, NULL) == 'n'
+ && BYTE (PRIVATE (a_this)->input, 9, NULL) == 't') {
+ SKIP_BYTES (a_this, 9);
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ return CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a num as defined in the css spec [4.1.1]:
+ *[0-9]+|[0-9]*\.[0-9]+
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_num out parameter. The parsed number.
+ *@return CR_OK upon successful completion,
+ *an error code otherwise.
+ *
+ *The CSS specification says that numbers may be
+ *preceded by '+' or '-' to indicate the sign.
+ *Technically, the "num" construction as defined
+ *by the tokenizer doesn't allow this, but we parse
+ *it here for simplicity.
+ */
+static enum CRStatus
+cr_tknzr_parse_num (CRTknzr * a_this,
+ CRNum ** a_num)
+{
+ enum CRStatus status = CR_PARSING_ERROR;
+ enum CRNumType val_type = NUM_GENERIC;
+ gboolean parsing_dec, /* true iff seen decimal point. */
+ parsed; /* true iff the substring seen so far is a valid CSS
+ number, i.e. `[0-9]+|[0-9]*\.[0-9]+'. */
+ guint32 cur_char = 0,
+ next_char = 0;
+ gdouble numerator, denominator = 1;
+ CRInputPos init_pos;
+ CRParsingLocation location = {0} ;
+ int sign = 1;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char == '+' || cur_char == '-') {
+ if (cur_char == '-') {
+ sign = -1;
+ }
+ READ_NEXT_CHAR (a_this, &cur_char);
+ }
+
+ if (IS_NUM (cur_char)) {
+ numerator = (cur_char - '0');
+ parsing_dec = FALSE;
+ parsed = TRUE;
+ } else if (cur_char == '.') {
+ numerator = 0;
+ parsing_dec = TRUE;
+ parsed = FALSE;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ cr_tknzr_get_parsing_location (a_this, &location) ;
+
+ for (;;) {
+ status = cr_tknzr_peek_char (a_this, &next_char);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR)
+ status = CR_OK;
+ break;
+ }
+ if (next_char == '.') {
+ if (parsing_dec) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+ parsing_dec = TRUE;
+ parsed = FALSE; /* In CSS, there must be at least
+ one digit after `.'. */
+ } else if (IS_NUM (next_char)) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ parsed = TRUE;
+
+ numerator = numerator * 10 + (cur_char - '0');
+ if (parsing_dec) {
+ denominator *= 10;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (!parsed) {
+ status = CR_PARSING_ERROR;
+ }
+
+ /*
+ *Now, set the output param values.
+ */
+ if (status == CR_OK) {
+ gdouble val = (numerator / denominator) * sign;
+ if (*a_num == NULL) {
+ *a_num = cr_num_new_with_val (val, val_type);
+
+ if (*a_num == NULL) {
+ status = CR_ERROR;
+ goto error;
+ }
+ } else {
+ (*a_num)->val = val;
+ (*a_num)->type = val_type;
+ }
+ cr_parsing_location_copy (&(*a_num)->location,
+ &location) ;
+ return CR_OK;
+ }
+
+ error:
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/*********************************************
+ *PUBLIC methods
+ ********************************************/
+
+CRTknzr *
+cr_tknzr_new (CRInput * a_input)
+{
+ CRTknzr *result = NULL;
+
+ result = g_try_malloc (sizeof (CRTknzr));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRTknzr));
+
+ result->priv = g_try_malloc (sizeof (CRTknzrPriv));
+
+ if (result->priv == NULL) {
+ cr_utils_trace_info ("Out of memory");
+
+ if (result) {
+ g_free (result);
+ result = NULL;
+ }
+
+ return NULL;
+ }
+ memset (result->priv, 0, sizeof (CRTknzrPriv));
+ if (a_input)
+ cr_tknzr_set_input (result, a_input);
+ return result;
+}
+
+CRTknzr *
+cr_tknzr_new_from_buf (guchar * a_buf, gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_at_destroy)
+{
+ CRTknzr *result = NULL;
+ CRInput *input = NULL;
+
+ input = cr_input_new_from_buf (a_buf, a_len, a_enc,
+ a_free_at_destroy);
+
+ g_return_val_if_fail (input != NULL, NULL);
+
+ result = cr_tknzr_new (input);
+
+ return result;
+}
+
+CRTknzr *
+cr_tknzr_new_from_uri (const guchar * a_file_uri,
+ enum CREncoding a_enc)
+{
+ CRTknzr *result = NULL;
+ CRInput *input = NULL;
+
+ input = cr_input_new_from_uri ((const gchar *) a_file_uri, a_enc);
+ g_return_val_if_fail (input != NULL, NULL);
+
+ result = cr_tknzr_new (input);
+
+ return result;
+}
+
+void
+cr_tknzr_ref (CRTknzr * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ PRIVATE (a_this)->ref_count++;
+}
+
+gboolean
+cr_tknzr_unref (CRTknzr * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE);
+
+ if (PRIVATE (a_this)->ref_count > 0) {
+ PRIVATE (a_this)->ref_count--;
+ }
+
+ if (PRIVATE (a_this)->ref_count == 0) {
+ cr_tknzr_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+enum CRStatus
+cr_tknzr_set_input (CRTknzr * a_this, CRInput * a_input)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->input) {
+ cr_input_unref (PRIVATE (a_this)->input);
+ }
+
+ PRIVATE (a_this)->input = a_input;
+
+ cr_input_ref (PRIVATE (a_this)->input);
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_tknzr_get_input (CRTknzr * a_this, CRInput ** a_input)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ *a_input = PRIVATE (a_this)->input;
+
+ return CR_OK;
+}
+
+/*********************************
+ *Tokenizer input handling routines
+ *********************************/
+
+/**
+ *Reads the next byte from the parser input stream.
+ *@param a_this the "this pointer" of the current instance of
+ *#CRParser.
+ *@param a_byte out parameter the place where to store the byte
+ *read.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_tknzr_read_byte (CRTknzr * a_this, guchar * a_byte)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ return cr_input_read_byte (PRIVATE (a_this)->input, a_byte);
+
+}
+
+/**
+ *Reads the next char from the parser input stream.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_char out parameter. The read char.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_tknzr_read_char (CRTknzr * a_this, guint32 * a_char)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_read_char (PRIVATE (a_this)->input, a_char);
+}
+
+/**
+ *Peeks a char from the parser input stream.
+ *To "peek a char" means reads the next char without consuming it.
+ *Subsequent calls to this function return the same char.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_char out parameter. The peeked char upon successful completion.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_tknzr_peek_char (CRTknzr * a_this, guint32 * a_char)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_peek_char (PRIVATE (a_this)->input, a_char);
+}
+
+/**
+ *Peeks a byte ahead at a given position in the parser input stream.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_offset the offset of the peeked byte starting from the current
+ *byte in the parser input stream.
+ *@param a_byte out parameter. The peeked byte upon
+ *successful completion.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_tknzr_peek_byte (CRTknzr * a_this, gulong a_offset, guchar * a_byte)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input && a_byte,
+ CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_peek_byte (PRIVATE (a_this)->input,
+ CR_SEEK_CUR, a_offset, a_byte);
+}
+
+/**
+ *Same as cr_tknzr_peek_byte() but this api returns the byte peeked.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_offset the offset of the peeked byte starting from the current
+ *byte in the parser input stream.
+ *@param a_eof out parameter. If not NULL, is set to TRUE if we reached end of
+ *file, FALE otherwise. If the caller sets it to NULL, this parameter
+ *is just ignored.
+ *@return the peeked byte.
+ */
+guchar
+cr_tknzr_peek_byte2 (CRTknzr * a_this, gulong a_offset, gboolean * a_eof)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, 0);
+
+ return cr_input_peek_byte2 (PRIVATE (a_this)->input, a_offset, a_eof);
+}
+
+/**
+ *Gets the number of bytes left in the topmost input stream
+ *associated to this parser.
+ *@param a_this the current instance of #CRTknzr
+ *@return the number of bytes left or -1 in case of error.
+ */
+glong
+cr_tknzr_get_nb_bytes_left (CRTknzr * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_get_nb_bytes_left (PRIVATE (a_this)->input);
+}
+
+enum CRStatus
+cr_tknzr_get_cur_pos (CRTknzr * a_this, CRInputPos * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_pos, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_get_cur_pos (PRIVATE (a_this)->input, a_pos);
+}
+
+enum CRStatus
+cr_tknzr_get_parsing_location (CRTknzr *a_this,
+ CRParsingLocation *a_loc)
+{
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_loc,
+ CR_BAD_PARAM_ERROR) ;
+
+ return cr_input_get_parsing_location
+ (PRIVATE (a_this)->input, a_loc) ;
+}
+
+enum CRStatus
+cr_tknzr_get_cur_byte_addr (CRTknzr * a_this, guchar ** a_addr)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_get_cur_byte_addr (PRIVATE (a_this)->input, a_addr);
+}
+
+enum CRStatus
+cr_tknzr_seek_index (CRTknzr * a_this, enum CRSeekPos a_origin, gint a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_seek_index (PRIVATE (a_this)->input, a_origin, a_pos);
+}
+
+enum CRStatus
+cr_tknzr_consume_chars (CRTknzr * a_this, guint32 a_char, glong * a_nb_char)
+{
+ gulong consumed = *(gulong *) a_nb_char;
+ enum CRStatus status;
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ status = cr_input_consume_chars (PRIVATE (a_this)->input,
+ a_char, &consumed);
+ *a_nb_char = (glong) consumed;
+ return status;
+}
+
+enum CRStatus
+cr_tknzr_set_cur_pos (CRTknzr * a_this, CRInputPos * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_set_cur_pos (PRIVATE (a_this)->input, a_pos);
+}
+
+enum CRStatus
+cr_tknzr_unget_token (CRTknzr * a_this, CRToken * a_token)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->token_cache == NULL,
+ CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->token_cache = a_token;
+
+ return CR_OK;
+}
+
+/**
+ *Returns the next token of the input stream.
+ *This method is really central. Each parsing
+ *method calls it.
+ *@param a_this the current tokenizer.
+ *@param a_tk out parameter. The returned token.
+ *for the sake of mem leak avoidance, *a_tk must
+ *be NULL.
+ *@param CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_tknzr_get_next_token (CRTknzr * a_this, CRToken ** a_tk)
+{
+ enum CRStatus status = CR_OK;
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ guint32 next_char = 0;
+ guchar next_bytes[4] = { 0 };
+ gboolean reached_eof = FALSE;
+ CRInput *input = NULL;
+ CRString *str = NULL;
+ CRRgb *rgb = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_tk && *a_tk == NULL
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ *a_tk = PRIVATE (a_this)->token_cache;
+ PRIVATE (a_this)->token_cache = NULL;
+ return CR_OK;
+ }
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_input_get_end_of_file
+ (PRIVATE (a_this)->input, &reached_eof);
+ ENSURE_PARSING_COND (status == CR_OK);
+
+ if (reached_eof == TRUE) {
+ status = CR_END_OF_INPUT_ERROR;
+ goto error;
+ }
+
+ input = PRIVATE (a_this)->input;
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ token = cr_token_new ();
+ ENSURE_PARSING_COND (token);
+
+ switch (next_char) {
+ case '@':
+ {
+ if (BYTE (input, 2, NULL) == 'f'
+ && BYTE (input, 3, NULL) == 'o'
+ && BYTE (input, 4, NULL) == 'n'
+ && BYTE (input, 5, NULL) == 't'
+ && BYTE (input, 6, NULL) == '-'
+ && BYTE (input, 7, NULL) == 'f'
+ && BYTE (input, 8, NULL) == 'a'
+ && BYTE (input, 9, NULL) == 'c'
+ && BYTE (input, 10, NULL) == 'e') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location
+ (a_this, &location) ;
+ SKIP_CHARS (a_this, 9);
+ status = cr_token_set_font_face_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'c'
+ && BYTE (input, 3, NULL) == 'h'
+ && BYTE (input, 4, NULL) == 'a'
+ && BYTE (input, 5, NULL) == 'r'
+ && BYTE (input, 6, NULL) == 's'
+ && BYTE (input, 7, NULL) == 'e'
+ && BYTE (input, 8, NULL) == 't') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location
+ (a_this, &location) ;
+ SKIP_CHARS (a_this, 7);
+ status = cr_token_set_charset_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'i'
+ && BYTE (input, 3, NULL) == 'm'
+ && BYTE (input, 4, NULL) == 'p'
+ && BYTE (input, 5, NULL) == 'o'
+ && BYTE (input, 6, NULL) == 'r'
+ && BYTE (input, 7, NULL) == 't') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location
+ (a_this, &location) ;
+ SKIP_CHARS (a_this, 6);
+ status = cr_token_set_import_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'm'
+ && BYTE (input, 3, NULL) == 'e'
+ && BYTE (input, 4, NULL) == 'd'
+ && BYTE (input, 5, NULL) == 'i'
+ && BYTE (input, 6, NULL) == 'a') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 5);
+ status = cr_token_set_media_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'p'
+ && BYTE (input, 3, NULL) == 'a'
+ && BYTE (input, 4, NULL) == 'g'
+ && BYTE (input, 5, NULL) == 'e') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 4);
+ status = cr_token_set_page_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ status = cr_tknzr_parse_atkeyword (a_this, &str);
+ if (status == CR_OK) {
+ status = cr_token_set_atkeyword (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ goto done;
+ }
+ }
+ break;
+
+ case 'u':
+
+ if (BYTE (input, 2, NULL) == 'r'
+ && BYTE (input, 3, NULL) == 'l'
+ && BYTE (input, 4, NULL) == '(') {
+ CRString *str2 = NULL;
+
+ status = cr_tknzr_parse_uri (a_this, &str2);
+ if (status == CR_OK) {
+ status = cr_token_set_uri (token, str2);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str2) {
+ cr_parsing_location_copy (&token->location,
+ &str2->location) ;
+ }
+ goto done;
+ }
+ }
+ goto fallback;
+ break;
+
+ case 'r':
+ if (BYTE (input, 2, NULL) == 'g'
+ && BYTE (input, 3, NULL) == 'b'
+ && BYTE (input, 4, NULL) == '(') {
+ status = cr_tknzr_parse_rgb (a_this, &rgb);
+ if (status == CR_OK && rgb) {
+ status = cr_token_set_rgb (token, rgb);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (rgb) {
+ cr_parsing_location_copy (&token->location,
+ &rgb->location) ;
+ }
+ rgb = NULL;
+ goto done;
+ }
+
+ }
+ goto fallback;
+ break;
+
+ case '<':
+ if (BYTE (input, 2, NULL) == '!'
+ && BYTE (input, 3, NULL) == '-'
+ && BYTE (input, 4, NULL) == '-') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 3);
+ status = cr_token_set_cdo (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '-':
+ if (BYTE (input, 2, NULL) == '-'
+ && BYTE (input, 3, NULL) == '>') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 2);
+ status = cr_token_set_cdc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ } else {
+ status = cr_tknzr_parse_ident
+ (a_this, &str);
+ if (status == CR_OK) {
+ cr_token_set_ident
+ (token, str);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ goto done;
+ } else {
+ goto parse_number;
+ }
+ }
+ break;
+
+ case '~':
+ if (BYTE (input, 2, NULL) == '=') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 1);
+ status = cr_token_set_includes (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '|':
+ if (BYTE (input, 2, NULL) == '=') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 1);
+ status = cr_token_set_dashmatch (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '/':
+ if (BYTE (input, 2, NULL) == '*') {
+ status = cr_tknzr_parse_comment (a_this, &str);
+
+ if (status == CR_OK) {
+ status = cr_token_set_comment (token, str);
+ str = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ goto done;
+ }
+ }
+ break ;
+
+ case ';':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_semicolon (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case '{':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_cbo (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ goto done;
+
+ case '}':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_cbc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case '(':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_po (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case ')':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_pc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case '[':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_bo (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case ']':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_bc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ {
+ guchar *start = NULL,
+ *end = NULL;
+
+ status = cr_tknzr_parse_w (a_this, &start,
+ &end, &location);
+ if (status == CR_OK) {
+ status = cr_token_set_s (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ goto done;
+ }
+ }
+ break;
+
+ case '#':
+ {
+ status = cr_tknzr_parse_hash (a_this, &str);
+ if (status == CR_OK && str) {
+ status = cr_token_set_hash (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ goto done;
+ }
+ }
+ break;
+
+ case '\'':
+ case '"':
+ status = cr_tknzr_parse_string (a_this, &str);
+ if (status == CR_OK && str) {
+ status = cr_token_set_string (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ goto done;
+ }
+ break;
+
+ case '!':
+ status = cr_tknzr_parse_important (a_this, &location);
+ if (status == CR_OK) {
+ status = cr_token_set_important_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ case '+':
+ /* '-' case is handled separately above for --> comments */
+ parse_number:
+ {
+ CRNum *num = NULL;
+
+ status = cr_tknzr_parse_num (a_this, &num);
+ if (status == CR_OK && num) {
+ next_bytes[0] = BYTE (input, 1, NULL);
+ next_bytes[1] = BYTE (input, 2, NULL);
+ next_bytes[2] = BYTE (input, 3, NULL);
+ next_bytes[3] = BYTE (input, 4, NULL);
+
+ if (next_bytes[0] == 'e'
+ && next_bytes[1] == 'm') {
+ num->type = NUM_LENGTH_EM;
+ status = cr_token_set_ems (token,
+ num);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'e'
+ && next_bytes[1] == 'x') {
+ num->type = NUM_LENGTH_EX;
+ status = cr_token_set_exs (token,
+ num);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'p'
+ && next_bytes[1] == 'x') {
+ num->type = NUM_LENGTH_PX;
+ status = cr_token_set_length
+ (token, num, LENGTH_PX_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'c'
+ && next_bytes[1] == 'm') {
+ num->type = NUM_LENGTH_CM;
+ status = cr_token_set_length
+ (token, num, LENGTH_CM_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'm'
+ && next_bytes[1] == 'm') {
+ num->type = NUM_LENGTH_MM;
+ status = cr_token_set_length
+ (token, num, LENGTH_MM_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'i'
+ && next_bytes[1] == 'n') {
+ num->type = NUM_LENGTH_IN;
+ status = cr_token_set_length
+ (token, num, LENGTH_IN_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'p'
+ && next_bytes[1] == 't') {
+ num->type = NUM_LENGTH_PT;
+ status = cr_token_set_length
+ (token, num, LENGTH_PT_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'p'
+ && next_bytes[1] == 'c') {
+ num->type = NUM_LENGTH_PC;
+ status = cr_token_set_length
+ (token, num, LENGTH_PC_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'd'
+ && next_bytes[1] == 'e'
+ && next_bytes[2] == 'g') {
+ num->type = NUM_ANGLE_DEG;
+ status = cr_token_set_angle
+ (token, num, ANGLE_DEG_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 3);
+ } else if (next_bytes[0] == 'r'
+ && next_bytes[1] == 'a'
+ && next_bytes[2] == 'd') {
+ num->type = NUM_ANGLE_RAD;
+ status = cr_token_set_angle
+ (token, num, ANGLE_RAD_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 3);
+ } else if (next_bytes[0] == 'g'
+ && next_bytes[1] == 'r'
+ && next_bytes[2] == 'a'
+ && next_bytes[3] == 'd') {
+ num->type = NUM_ANGLE_GRAD;
+ status = cr_token_set_angle
+ (token, num, ANGLE_GRAD_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 4);
+ } else if (next_bytes[0] == 'm'
+ && next_bytes[1] == 's') {
+ num->type = NUM_TIME_MS;
+ status = cr_token_set_time
+ (token, num, TIME_MS_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 's') {
+ num->type = NUM_TIME_S;
+ status = cr_token_set_time
+ (token, num, TIME_S_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 1);
+ } else if (next_bytes[0] == 'H'
+ && next_bytes[1] == 'z') {
+ num->type = NUM_FREQ_HZ;
+ status = cr_token_set_freq
+ (token, num, FREQ_HZ_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'k'
+ && next_bytes[1] == 'H'
+ && next_bytes[2] == 'z') {
+ num->type = NUM_FREQ_KHZ;
+ status = cr_token_set_freq
+ (token, num, FREQ_KHZ_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 3);
+ } else if (next_bytes[0] == '%') {
+ num->type = NUM_PERCENTAGE;
+ status = cr_token_set_percentage
+ (token, num);
+ num = NULL;
+ SKIP_CHARS (a_this, 1);
+ } else {
+ status = cr_tknzr_parse_ident (a_this,
+ &str);
+ if (status == CR_OK && str) {
+ num->type = NUM_UNKNOWN_TYPE;
+ status = cr_token_set_dimen
+ (token, num, str);
+ num = NULL;
+ CHECK_PARSING_STATUS (status,
+ TRUE);
+ str = NULL;
+ } else {
+ status = cr_token_set_number
+ (token, num);
+ num = NULL;
+ CHECK_PARSING_STATUS (status, CR_OK);
+ str = NULL;
+ }
+ }
+ if (token && token->u.num) {
+ cr_parsing_location_copy (&token->location,
+ &token->u.num->location) ;
+ } else {
+ status = CR_ERROR ;
+ }
+ goto done ;
+ }
+ }
+ break;
+
+ default:
+ fallback:
+ /*process the fallback cases here */
+
+ if (next_char == '\\'
+ || (cr_utils_is_nonascii (next_bytes[0]) == TRUE)
+ || ((next_char >= 'a') && (next_char <= 'z'))
+ || ((next_char >= 'A') && (next_char <= 'Z'))) {
+ status = cr_tknzr_parse_ident (a_this, &str);
+ if (status == CR_OK && str) {
+ guint32 next_c = 0;
+
+ status = cr_input_peek_char
+ (PRIVATE (a_this)->input, &next_c);
+
+ if (status == CR_OK && next_c == '(') {
+
+ SKIP_CHARS (a_this, 1);
+ status = cr_token_set_function
+ (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ /*ownership is transferred
+ *to token by cr_token_set_function.
+ */
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ } else {
+ status = cr_token_set_ident (token,
+ str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ }
+ goto done;
+ } else {
+ if (str) {
+ cr_string_destroy (str);
+ str = NULL;
+ }
+ }
+ }
+ break;
+ }
+
+ READ_NEXT_CHAR (a_this, &next_char);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_delim (token, next_char);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ done:
+
+ if (status == CR_OK && token) {
+ *a_tk = token;
+ /*
+ *store the previous position input stream pos.
+ */
+ memmove (&PRIVATE (a_this)->prev_pos,
+ &init_pos, sizeof (CRInputPos));
+ return CR_OK;
+ }
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (str) {
+ cr_string_destroy (str);
+ str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+
+}
+
+enum CRStatus
+cr_tknzr_parse_token (CRTknzr * a_this, enum CRTokenType a_type,
+ enum CRTokenExtraType a_et, gpointer a_res,
+ gpointer a_extra_res)
+{
+ enum CRStatus status = CR_OK;
+ CRToken *token = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_res, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_get_next_token (a_this, &token);
+ if (status != CR_OK)
+ return status;
+ if (token == NULL)
+ return CR_PARSING_ERROR;
+
+ if (token->type == a_type) {
+ switch (a_type) {
+ case NO_TK:
+ case S_TK:
+ case CDO_TK:
+ case CDC_TK:
+ case INCLUDES_TK:
+ case DASHMATCH_TK:
+ case IMPORT_SYM_TK:
+ case PAGE_SYM_TK:
+ case MEDIA_SYM_TK:
+ case FONT_FACE_SYM_TK:
+ case CHARSET_SYM_TK:
+ case IMPORTANT_SYM_TK:
+ status = CR_OK;
+ break;
+
+ case STRING_TK:
+ case IDENT_TK:
+ case HASH_TK:
+ case ATKEYWORD_TK:
+ case FUNCTION_TK:
+ case COMMENT_TK:
+ case URI_TK:
+ *((CRString **) a_res) = token->u.str;
+ token->u.str = NULL;
+ status = CR_OK;
+ break;
+
+ case EMS_TK:
+ case EXS_TK:
+ case PERCENTAGE_TK:
+ case NUMBER_TK:
+ *((CRNum **) a_res) = token->u.num;
+ token->u.num = NULL;
+ status = CR_OK;
+ break;
+
+ case LENGTH_TK:
+ case ANGLE_TK:
+ case TIME_TK:
+ case FREQ_TK:
+ if (token->extra_type == a_et) {
+ *((CRNum **) a_res) = token->u.num;
+ token->u.num = NULL;
+ status = CR_OK;
+ }
+ break;
+
+ case DIMEN_TK:
+ *((CRNum **) a_res) = token->u.num;
+ if (a_extra_res == NULL) {
+ status = CR_BAD_PARAM_ERROR;
+ goto error;
+ }
+
+ *((CRString **) a_extra_res) = token->dimen;
+ token->u.num = NULL;
+ token->dimen = NULL;
+ status = CR_OK;
+ break;
+
+ case DELIM_TK:
+ *((guint32 *) a_res) = token->u.unichar;
+ status = CR_OK;
+ break;
+
+ case UNICODERANGE_TK:
+ default:
+ status = CR_PARSING_ERROR;
+ break;
+ }
+
+ cr_token_destroy (token);
+ token = NULL;
+ } else {
+ cr_tknzr_unget_token (a_this, token);
+ token = NULL;
+ status = CR_PARSING_ERROR;
+ }
+
+ return status;
+
+ error:
+
+ if (token) {
+ cr_tknzr_unget_token (a_this, token);
+ token = NULL;
+ }
+
+ return status;
+}
+
+void
+cr_tknzr_destroy (CRTknzr * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (PRIVATE (a_this) && PRIVATE (a_this)->input) {
+ if (cr_input_unref (PRIVATE (a_this)->input)
+ == TRUE) {
+ PRIVATE (a_this)->input = NULL;
+ }
+ }
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ if (PRIVATE (a_this)) {
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-tknzr.h b/src/st/croco/cr-tknzr.h
new file mode 100644
index 0000000..13985b3
--- /dev/null
+++ b/src/st/croco/cr-tknzr.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for coypyright information.
+ */
+
+/**
+ *@file
+ *The declaration of the #CRTknzr (tokenizer)
+ *class.
+ */
+
+#ifndef __CR_TKNZR_H__
+#define __CR_TKNZR_H__
+
+#include "cr-utils.h"
+#include "cr-input.h"
+#include "cr-token.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRTknzr CRTknzr ;
+typedef struct _CRTknzrPriv CRTknzrPriv ;
+
+/**
+ *The tokenizer is the class that knows
+ *about all the css token. Its main job is
+ *to return the next token found in the character
+ *input stream.
+ */
+struct _CRTknzr
+{
+ /*the private data of the tokenizer.*/
+ CRTknzrPriv *priv ;
+} ;
+
+CRTknzr * cr_tknzr_new (CRInput *a_input) ;
+
+CRTknzr * cr_tknzr_new_from_uri (const guchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+CRTknzr * cr_tknzr_new_from_buf (guchar *a_buf, gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_at_destroy) ;
+
+gboolean cr_tknzr_unref (CRTknzr *a_this) ;
+
+void cr_tknzr_ref (CRTknzr *a_this) ;
+
+enum CRStatus cr_tknzr_read_byte (CRTknzr *a_this, guchar *a_byte) ;
+
+enum CRStatus cr_tknzr_read_char (CRTknzr *a_this, guint32 *a_char);
+
+enum CRStatus cr_tknzr_peek_char (CRTknzr *a_this, guint32 *a_char) ;
+
+enum CRStatus cr_tknzr_peek_byte (CRTknzr *a_this, gulong a_offset,
+ guchar *a_byte) ;
+
+guchar cr_tknzr_peek_byte2 (CRTknzr *a_this, gulong a_offset,
+ gboolean *a_eof) ;
+
+enum CRStatus cr_tknzr_set_cur_pos (CRTknzr *a_this, CRInputPos *a_pos) ;
+
+glong cr_tknzr_get_nb_bytes_left (CRTknzr *a_this) ;
+
+enum CRStatus cr_tknzr_get_cur_pos (CRTknzr *a_this, CRInputPos *a_pos) ;
+
+enum CRStatus cr_tknzr_get_parsing_location (CRTknzr *a_this,
+ CRParsingLocation *a_loc) ;
+
+enum CRStatus cr_tknzr_seek_index (CRTknzr *a_this,
+ enum CRSeekPos a_origin,
+ gint a_pos) ;
+
+enum CRStatus cr_tknzr_get_cur_byte_addr (CRTknzr *a_this, guchar **a_addr) ;
+
+
+enum CRStatus cr_tknzr_consume_chars (CRTknzr *a_this, guint32 a_char,
+ glong *a_nb_char) ;
+
+enum CRStatus cr_tknzr_get_next_token (CRTknzr *a_this, CRToken ** a_tk) ;
+
+enum CRStatus cr_tknzr_unget_token (CRTknzr *a_this, CRToken *a_token) ;
+
+
+enum CRStatus cr_tknzr_parse_token (CRTknzr *a_this, enum CRTokenType a_type,
+ enum CRTokenExtraType a_et, gpointer a_res,
+ gpointer a_extra_res) ;
+enum CRStatus cr_tknzr_set_input (CRTknzr *a_this, CRInput *a_input) ;
+
+enum CRStatus cr_tknzr_get_input (CRTknzr *a_this, CRInput **a_input) ;
+
+void cr_tknzr_destroy (CRTknzr *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_TKZNR_H__*/
diff --git a/src/st/croco/cr-token.c b/src/st/croco/cr-token.c
new file mode 100644
index 0000000..91dd632
--- /dev/null
+++ b/src/st/croco/cr-token.c
@@ -0,0 +1,636 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * see COPYRIGHTS file for copyright information.
+ */
+
+/**
+ *@file
+ *The definition of the #CRToken class.
+ *Abstracts a css2 token.
+ */
+#include <string.h>
+#include "cr-token.h"
+
+/*
+ *TODO: write a CRToken::to_string() method.
+ */
+
+/**
+ *Frees the attributes of the current instance
+ *of #CRtoken.
+ *@param a_this the current instance of #CRToken.
+ */
+static void
+cr_token_clear (CRToken * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case S_TK:
+ case CDO_TK:
+ case CDC_TK:
+ case INCLUDES_TK:
+ case DASHMATCH_TK:
+ case PAGE_SYM_TK:
+ case MEDIA_SYM_TK:
+ case FONT_FACE_SYM_TK:
+ case CHARSET_SYM_TK:
+ case IMPORT_SYM_TK:
+ case IMPORTANT_SYM_TK:
+ case SEMICOLON_TK:
+ case NO_TK:
+ case DELIM_TK:
+ case CBO_TK:
+ case CBC_TK:
+ case BO_TK:
+ case BC_TK:
+ break;
+
+ case STRING_TK:
+ case IDENT_TK:
+ case HASH_TK:
+ case URI_TK:
+ case FUNCTION_TK:
+ case COMMENT_TK:
+ case ATKEYWORD_TK:
+ if (a_this->u.str) {
+ cr_string_destroy (a_this->u.str);
+ a_this->u.str = NULL;
+ }
+ break;
+
+ case EMS_TK:
+ case EXS_TK:
+ case LENGTH_TK:
+ case ANGLE_TK:
+ case TIME_TK:
+ case FREQ_TK:
+ case PERCENTAGE_TK:
+ case NUMBER_TK:
+ case PO_TK:
+ case PC_TK:
+ if (a_this->u.num) {
+ cr_num_destroy (a_this->u.num);
+ a_this->u.num = NULL;
+ }
+ break;
+
+ case DIMEN_TK:
+ if (a_this->u.num) {
+ cr_num_destroy (a_this->u.num);
+ a_this->u.num = NULL;
+ }
+
+ if (a_this->dimen) {
+ cr_string_destroy (a_this->dimen);
+ a_this->dimen = NULL;
+ }
+
+ break;
+
+ case RGB_TK:
+ if (a_this->u.rgb) {
+ cr_rgb_destroy (a_this->u.rgb) ;
+ a_this->u.rgb = NULL ;
+ }
+ break ;
+
+ case UNICODERANGE_TK:
+ /*not supported yet. */
+ break;
+
+ default:
+ cr_utils_trace_info ("I don't know how to clear this token\n") ;
+ break;
+ }
+
+ a_this->type = NO_TK;
+}
+
+/**
+ *Default constructor of
+ *the #CRToken class.
+ *@return the newly built instance of #CRToken.
+ */
+CRToken *
+cr_token_new (void)
+{
+ CRToken *result = NULL;
+
+ result = g_try_malloc (sizeof (CRToken));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRToken));
+
+ return result;
+}
+
+/**
+ *Sets the type of curren instance of
+ *#CRToken to 'S_TK' (S in the css2 spec)
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_s (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = S_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current instance of
+ *#CRToken to 'CDO_TK' (CDO as said by the css2 spec)
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_cdo (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CDO_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current token to
+ *CDC_TK (CDC as said by the css2 spec).
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_cdc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CDC_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current instance of
+ *#CRToken to INCLUDES_TK (INCLUDES as said by the css2 spec).
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_includes (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = INCLUDES_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current instance of
+ *#CRToken to DASHMATCH_TK (DASHMATCH as said by the css2 spec).
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_dashmatch (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = DASHMATCH_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_comment (CRToken * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = COMMENT_TK;
+ a_this->u.str = a_str ;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_string (CRToken * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = STRING_TK;
+
+ a_this->u.str = a_str ;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_ident (CRToken * a_this, CRString * a_ident)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = IDENT_TK;
+ a_this->u.str = a_ident;
+ return CR_OK;
+}
+
+
+enum CRStatus
+cr_token_set_function (CRToken * a_this, CRString * a_fun_name)
+{
+ g_return_val_if_fail (a_this,
+ CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = FUNCTION_TK;
+ a_this->u.str = a_fun_name;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_hash (CRToken * a_this, CRString * a_hash)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = HASH_TK;
+ a_this->u.str = a_hash;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_rgb (CRToken * a_this, CRRgb * a_rgb)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = RGB_TK;
+ a_this->u.rgb = a_rgb;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_import_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = IMPORT_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_page_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PAGE_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_media_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = MEDIA_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_font_face_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = FONT_FACE_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_charset_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = CHARSET_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_atkeyword (CRToken * a_this, CRString * a_atname)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = ATKEYWORD_TK;
+ a_this->u.str = a_atname;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_important_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = IMPORTANT_SYM_TK;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_ems (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = EMS_TK;
+ a_this->u.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_exs (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = EXS_TK;
+ a_this->u.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_length (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = LENGTH_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_angle (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = ANGLE_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_time (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = TIME_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_freq (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = FREQ_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_dimen (CRToken * a_this, CRNum * a_num,
+ CRString * a_dim)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = DIMEN_TK;
+ a_this->u.num = a_num;
+ a_this->dimen = a_dim;
+ return CR_OK;
+
+}
+
+enum CRStatus
+cr_token_set_percentage (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PERCENTAGE_TK;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_number (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = NUMBER_TK;
+ a_this->u.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_uri (CRToken * a_this, CRString * a_uri)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = URI_TK;
+ a_this->u.str = a_uri;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_delim (CRToken * a_this, guint32 a_char)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = DELIM_TK;
+ a_this->u.unichar = a_char;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_semicolon (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = SEMICOLON_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_cbo (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CBO_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_cbc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CBC_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_po (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PO_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_pc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PC_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_bo (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = BO_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_bc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = BC_TK;
+
+ return CR_OK;
+}
+
+/**
+ *The destructor of the #CRToken class.
+ *@param a_this the current instance of #CRToken.
+ */
+void
+cr_token_destroy (CRToken * a_this)
+{
+ g_return_if_fail (a_this);
+
+ cr_token_clear (a_this);
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-token.h b/src/st/croco/cr-token.h
new file mode 100644
index 0000000..f1257b7
--- /dev/null
+++ b/src/st/croco/cr-token.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_TOKEN_H__
+#define __CR_TOKEN_H__
+
+#include "cr-utils.h"
+#include "cr-input.h"
+#include "cr-num.h"
+#include "cr-rgb.h"
+#include "cr-string.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+enum CRTokenType
+{
+ NO_TK,
+ S_TK,
+ CDO_TK,
+ CDC_TK,
+ INCLUDES_TK,
+ DASHMATCH_TK,
+ COMMENT_TK,
+ STRING_TK,
+ IDENT_TK,
+ HASH_TK,
+ IMPORT_SYM_TK,
+ PAGE_SYM_TK,
+ MEDIA_SYM_TK,
+ FONT_FACE_SYM_TK,
+ CHARSET_SYM_TK,
+ ATKEYWORD_TK,
+ IMPORTANT_SYM_TK,
+ EMS_TK,
+ EXS_TK,
+ LENGTH_TK,
+ ANGLE_TK,
+ TIME_TK,
+ FREQ_TK,
+ DIMEN_TK,
+ PERCENTAGE_TK,
+ NUMBER_TK,
+ RGB_TK,
+ URI_TK,
+ FUNCTION_TK,
+ UNICODERANGE_TK,
+ SEMICOLON_TK,
+ CBO_TK, /*opening curly bracket*/
+ CBC_TK, /*closing curly bracket*/
+ PO_TK, /*opening parenthesis*/
+ PC_TK, /*closing parenthesis*/
+ BO_TK, /*opening bracket*/
+ BC_TK, /*closing bracket*/
+ DELIM_TK
+} ;
+
+enum CRTokenExtraType
+{
+ NO_ET = 0,
+ LENGTH_PX_ET,
+ LENGTH_CM_ET,
+ LENGTH_MM_ET,
+ LENGTH_IN_ET,
+ LENGTH_PT_ET,
+ LENGTH_PC_ET,
+ ANGLE_DEG_ET,
+ ANGLE_RAD_ET,
+ ANGLE_GRAD_ET,
+ TIME_MS_ET,
+ TIME_S_ET,
+ FREQ_HZ_ET,
+ FREQ_KHZ_ET
+} ;
+
+typedef struct _CRToken CRToken ;
+
+/**
+ *This class abstracts a css2 token.
+ */
+struct _CRToken
+{
+ enum CRTokenType type ;
+ enum CRTokenExtraType extra_type ;
+ CRInputPos pos ;
+
+ union
+ {
+ CRString *str ;
+ CRRgb *rgb ;
+ CRNum *num ;
+ guint32 unichar ;
+ } u ;
+
+ CRString * dimen ;
+ CRParsingLocation location ;
+} ;
+
+CRToken* cr_token_new (void) ;
+
+enum CRStatus cr_token_set_s (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cdo (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cdc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_includes (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_dashmatch (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_comment (CRToken *a_this, CRString *a_str) ;
+
+enum CRStatus cr_token_set_string (CRToken *a_this, CRString *a_str) ;
+
+enum CRStatus cr_token_set_ident (CRToken *a_this, CRString * a_ident) ;
+
+enum CRStatus cr_token_set_hash (CRToken *a_this, CRString *a_hash) ;
+
+enum CRStatus cr_token_set_rgb (CRToken *a_this, CRRgb *a_rgb) ;
+
+enum CRStatus cr_token_set_import_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_page_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_media_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_font_face_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_charset_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_atkeyword (CRToken *a_this, CRString *a_atname) ;
+
+enum CRStatus cr_token_set_important_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_ems (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_exs (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_length (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_angle (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_time (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_freq (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_dimen (CRToken *a_this, CRNum *a_num,
+ CRString *a_dim) ;
+
+enum CRStatus cr_token_set_percentage (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_number (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_uri (CRToken *a_this, CRString *a_uri) ;
+
+enum CRStatus cr_token_set_function (CRToken *a_this,
+ CRString *a_fun_name) ;
+
+enum CRStatus cr_token_set_bc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_bo (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_po (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_pc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cbc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cbo (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_semicolon (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_delim (CRToken *a_this, guint32 a_char) ;
+
+
+/*
+ enum CRStatus
+ cr_token_set_unicoderange (CRToken *a_this,
+ CRUnicodeRange *a_range) ;
+*/
+
+void
+cr_token_destroy (CRToken *a_this) ;
+
+
+G_END_DECLS
+
+#endif /*__CR_TOKEN_H__*/
diff --git a/src/st/croco/cr-utils.c b/src/st/croco/cr-utils.c
new file mode 100644
index 0000000..5fafade
--- /dev/null
+++ b/src/st/croco/cr-utils.c
@@ -0,0 +1,1330 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include "cr-utils.h"
+#include "cr-string.h"
+
+/**
+ *@file:
+ *Some misc utility functions used
+ *in the libcroco.
+ *Note that troughout this file I will
+ *refer to the CSS SPECIFICATIONS DOCUMENTATION
+ *written by the w3c guys. You can find that document
+ *at http://www.w3.org/TR/REC-CSS2/ .
+ */
+
+/****************************
+ *Encoding transformations and
+ *encoding helpers
+ ****************************/
+
+/*
+ *Here is the correspondence between the ucs-4 charactere codes
+ *and there matching utf-8 encoding pattern as described by RFC 2279:
+ *
+ *UCS-4 range (hex.) UTF-8 octet sequence (binary)
+ *------------------ -----------------------------
+ *0000 0000-0000 007F 0xxxxxxx
+ *0000 0080-0000 07FF 110xxxxx 10xxxxxx
+ *0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
+ *0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ *0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ *0400 0000-7FFF FFFF 1111110x 10xxxxxx ... 10xxxxxx
+ */
+
+/**
+ *Given an utf8 string buffer, calculates
+ *the length of this string if it was encoded
+ *in ucs4.
+ *@param a_in_start a pointer to the beginning of
+ *the input utf8 string.
+ *@param a_in_end a pointre to the end of the input
+ *utf8 string (points to the last byte of the buffer)
+ *@param a_len out parameter the calculated length.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs4 (const guchar * a_in_start,
+ const guchar * a_in_end, gulong * a_len)
+{
+ guchar *byte_ptr = NULL;
+ gint len = 0;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint c = 0;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+ *a_len = 0;
+
+ for (byte_ptr = (guchar *) a_in_start;
+ byte_ptr <= a_in_end; byte_ptr++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (*byte_ptr <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = *byte_ptr;
+ nb_bytes_2_decode = 1;
+
+ } else if ((*byte_ptr & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((*byte_ptr & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((*byte_ptr & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((*byte_ptr & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((*byte_ptr & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*
+ *BAD ENCODING
+ */
+ return CR_ENCODING_ERROR;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ byte_ptr++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((*byte_ptr & 0xC0) != 0x80) {
+ return CR_ENCODING_ERROR;
+ }
+
+ c = (c << 6) | (*byte_ptr & 0x3F);
+ }
+
+ len++;
+ }
+
+ *a_len = len;
+
+ return CR_OK;
+}
+
+/**
+ *Given an ucs4 string, this function
+ *returns the size (in bytes) this string
+ *would have occupied if it was encoded in utf-8.
+ *@param a_in_start a pointer to the beginning of the input
+ *buffer.
+ *@param a_in_end a pointer to the end of the input buffer.
+ *@param a_len out parameter. The computed length.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs4_str_len_as_utf8 (const guint32 * a_in_start,
+ const guint32 * a_in_end, gulong * a_len)
+{
+ gint len = 0;
+ guint32 *char_ptr = NULL;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+
+ for (char_ptr = (guint32 *) a_in_start;
+ char_ptr <= a_in_end; char_ptr++) {
+ if (*char_ptr <= 0x7F) {
+ /*the utf-8 char would take 1 byte */
+ len += 1;
+ } else if (*char_ptr <= 0x7FF) {
+ /*the utf-8 char would take 2 bytes */
+ len += 2;
+ } else if (*char_ptr <= 0xFFFF) {
+ len += 3;
+ } else if (*char_ptr <= 0x1FFFFF) {
+ len += 4;
+ } else if (*char_ptr <= 0x3FFFFFF) {
+ len += 5;
+ } else if (*char_ptr <= 0x7FFFFFFF) {
+ len += 6;
+ }
+ }
+
+ *a_len = len;
+ return CR_OK;
+}
+
+/**
+ *Given an ucsA string, this function
+ *returns the size (in bytes) this string
+ *would have occupied if it was encoded in utf-8.
+ *@param a_in_start a pointer to the beginning of the input
+ *buffer.
+ *@param a_in_end a pointer to the end of the input buffer.
+ *@param a_len out parameter. The computed length.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs1_str_len_as_utf8 (const guchar * a_in_start,
+ const guchar * a_in_end, gulong * a_len)
+{
+ gint len = 0;
+ guchar *char_ptr = NULL;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+
+ for (char_ptr = (guchar *) a_in_start;
+ char_ptr <= a_in_end; char_ptr++) {
+ if (*char_ptr <= 0x7F) {
+ /*the utf-8 char would take 1 byte */
+ len += 1;
+ } else {
+ /*the utf-8 char would take 2 bytes */
+ len += 2;
+ }
+ }
+
+ *a_len = len;
+ return CR_OK;
+}
+
+/**
+ *Converts an utf8 buffer into an ucs4 buffer.
+ *
+ *@param a_in the input utf8 buffer to convert.
+ *@param a_in_len in/out parameter. The size of the
+ *input buffer to convert. After return, this parameter contains
+ *the actual number of bytes consumed.
+ *@param a_out the output converted ucs4 buffer. Must be allocated by
+ *the caller.
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *If this size is actually smaller than the real needed size, the function
+ *just converts what it can and returns a success status. After return,
+ *this param points to the actual number of characters decoded.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_utf8_to_ucs4 (const guchar * a_in,
+ gulong * a_in_len, guint32 * a_out, gulong * a_out_len)
+{
+ gulong in_len = 0,
+ out_len = 0,
+ in_index = 0,
+ out_index = 0;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint c = 0;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ status = CR_OK;
+ goto end;
+ }
+
+ in_len = *a_in_len;
+ out_len = *a_out_len;
+
+ for (in_index = 0, out_index = 0;
+ (in_index < in_len) && (out_index < out_len);
+ in_index++, out_index++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (a_in[in_index] <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = a_in[in_index];
+ nb_bytes_2_decode = 1;
+
+ } else if ((a_in[in_index] & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((a_in[in_index] & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((a_in[in_index] & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((a_in[in_index] & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((a_in[in_index] & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*BAD ENCODING */
+ goto end;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ in_index++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((a_in[in_index] & 0xC0) != 0x80) {
+ goto end;
+ }
+
+ c = (c << 6) | (a_in[in_index] & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ /************************
+ *Some security tests
+ ***********************/
+
+ /*be sure c is a char */
+ if (c == 0xFFFF || c == 0xFFFE)
+ goto end;
+
+ /*be sure c is inferior to the max ucs4 char value */
+ if (c > 0x10FFFF)
+ goto end;
+
+ /*
+ *c must be less than UTF16 "lower surrogate begin"
+ *or higher than UTF16 "High surrogate end"
+ */
+ if (c >= 0xD800 && c <= 0xDFFF)
+ goto end;
+
+ /*Avoid characters that equals zero */
+ if (c == 0)
+ goto end;
+
+ a_out[out_index] = c;
+ }
+
+ end:
+ *a_out_len = out_index + 1;
+ *a_in_len = in_index + 1;
+
+ return status;
+}
+
+/**
+ *Reads a character from an utf8 buffer.
+ *Actually decode the next character code (unicode character code)
+ *and returns it.
+ *@param a_in the starting address of the utf8 buffer.
+ *@param a_in_len the length of the utf8 buffer.
+ *@param a_out output parameter. The resulting read char.
+ *@param a_consumed the number of the bytes consumed to
+ *decode the returned character code.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_read_char_from_utf8_buf (const guchar * a_in,
+ gulong a_in_len,
+ guint32 * a_out, gulong * a_consumed)
+{
+ gulong in_index = 0,
+ nb_bytes_2_decode = 0;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint32 c = 0;
+
+ g_return_val_if_fail (a_in && a_out && a_out
+ && a_consumed, CR_BAD_PARAM_ERROR);
+
+ if (a_in_len < 1) {
+ status = CR_OK;
+ goto end;
+ }
+
+ if (*a_in <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = *a_in;
+ nb_bytes_2_decode = 1;
+
+ } else if ((*a_in & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = *a_in & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((*a_in & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((*a_in & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((*a_in & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((*a_in & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*BAD ENCODING */
+ goto end;
+ }
+
+ if (nb_bytes_2_decode > a_in_len) {
+ status = CR_END_OF_INPUT_ERROR;
+ goto end;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (in_index = 1; in_index < nb_bytes_2_decode; in_index++) {
+ /*byte pattern must be: 10xx xxxx */
+ if ((a_in[in_index] & 0xC0) != 0x80) {
+ goto end;
+ }
+
+ c = (c << 6) | (a_in[in_index] & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ /************************
+ *Some security tests
+ ***********************/
+
+ /*be sure c is a char */
+ if (c == 0xFFFF || c == 0xFFFE)
+ goto end;
+
+ /*be sure c is inferior to the max ucs4 char value */
+ if (c > 0x10FFFF)
+ goto end;
+
+ /*
+ *c must be less than UTF16 "lower surrogate begin"
+ *or higher than UTF16 "High surrogate end"
+ */
+ if (c >= 0xD800 && c <= 0xDFFF)
+ goto end;
+
+ /*Avoid characters that equals zero */
+ if (c == 0)
+ goto end;
+
+ *a_out = c;
+
+ end:
+ *a_consumed = nb_bytes_2_decode;
+
+ return status;
+}
+
+/**
+ *
+ */
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs1 (const guchar * a_in_start,
+ const guchar * a_in_end, gulong * a_len)
+{
+ /*
+ *Note: this function can be made shorter
+ *but it considers all the cases of the utf8 encoding
+ *to ease further extensions ...
+ */
+
+ guchar *byte_ptr = NULL;
+ gint len = 0;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint c = 0;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+ *a_len = 0;
+
+ for (byte_ptr = (guchar *) a_in_start;
+ byte_ptr <= a_in_end; byte_ptr++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (*byte_ptr <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = *byte_ptr;
+ nb_bytes_2_decode = 1;
+
+ } else if ((*byte_ptr & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((*byte_ptr & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((*byte_ptr & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((*byte_ptr & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((*byte_ptr & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*
+ *BAD ENCODING
+ */
+ return CR_ENCODING_ERROR;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ byte_ptr++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((*byte_ptr & 0xC0) != 0x80) {
+ return CR_ENCODING_ERROR;
+ }
+
+ c = (c << 6) | (*byte_ptr & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ if (c <= 0xFF) { /*Add other conditions to support
+ *other char sets (ucs2, ucs3, ucs4).
+ */
+ len++;
+ } else {
+ /*the char is too long to fit
+ *into the supposed charset len.
+ */
+ return CR_ENCODING_ERROR;
+ }
+ }
+
+ *a_len = len;
+
+ return CR_OK;
+}
+
+/**
+ *Converts an utf8 string into an ucs4 string.
+ *@param a_in the input string to convert.
+ *@param a_in_len in/out parameter. The length of the input
+ *string. After return, points to the actual number of bytes
+ *consumed. This can be useful to debug the input stream in case
+ *of encoding error.
+ *@param a_out out parameter. Points to the output string. It is allocated
+ *by this function and must be freed by the caller.
+ *@param a_out_len out parameter. The length of the output string.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *
+ */
+enum CRStatus
+cr_utils_utf8_str_to_ucs4 (const guchar * a_in,
+ gulong * a_in_len,
+ guint32 ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ status = cr_utils_utf8_str_len_as_ucs4 (a_in,
+ &a_in[*a_in_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ *a_out = g_malloc0 (*a_out_len * sizeof (guint32));
+
+ status = cr_utils_utf8_to_ucs4 (a_in, a_in_len, *a_out, a_out_len);
+
+ return status;
+}
+
+/**
+ *Converts an ucs4 buffer into an utf8 buffer.
+ *
+ *@param a_in the input ucs4 buffer to convert.
+ *@param a_in_len in/out parameter. The size of the
+ *input buffer to convert. After return, this parameter contains
+ *the actual number of characters consumed.
+ *@param a_out the output converted utf8 buffer. Must be allocated by
+ *the caller.
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *If this size is actually smaller than the real needed size, the function
+ *just converts what it can and returns a success status. After return,
+ *this param points to the actual number of bytes in the buffer.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs4_to_utf8 (const guint32 * a_in,
+ gulong * a_in_len, guchar * a_out, gulong * a_out_len)
+{
+ gulong in_len = 0,
+ in_index = 0,
+ out_index = 0;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len && a_out && a_out_len,
+ CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ status = CR_OK;
+ goto end;
+ }
+
+ in_len = *a_in_len;
+
+ for (in_index = 0; in_index < in_len; in_index++) {
+ /*
+ *FIXME: return whenever we encounter forbidden char values.
+ */
+
+ if (a_in[in_index] <= 0x7F) {
+ a_out[out_index] = a_in[in_index];
+ out_index++;
+ } else if (a_in[in_index] <= 0x7FF) {
+ a_out[out_index] = (0xC0 | (a_in[in_index] >> 6));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 2;
+ } else if (a_in[in_index] <= 0xFFFF) {
+ a_out[out_index] = (0xE0 | (a_in[in_index] >> 12));
+ a_out[out_index + 1] =
+ (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 2] =
+ (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 3;
+ } else if (a_in[in_index] <= 0x1FFFFF) {
+ a_out[out_index] = (0xF0 | (a_in[in_index] >> 18));
+ a_out[out_index + 1]
+ = (0x80 | ((a_in[in_index] >> 12) & 0x3F));
+ a_out[out_index + 2]
+ = (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 3]
+ = (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 4;
+ } else if (a_in[in_index] <= 0x3FFFFFF) {
+ a_out[out_index] = (0xF8 | (a_in[in_index] >> 24));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] >> 18));
+ a_out[out_index + 2]
+ = (0x80 | ((a_in[in_index] >> 12) & 0x3F));
+ a_out[out_index + 3]
+ = (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 4]
+ = (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 5;
+ } else if (a_in[in_index] <= 0x7FFFFFFF) {
+ a_out[out_index] = (0xFC | (a_in[in_index] >> 30));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] >> 24));
+ a_out[out_index + 2]
+ = (0x80 | ((a_in[in_index] >> 18) & 0x3F));
+ a_out[out_index + 3]
+ = (0x80 | ((a_in[in_index] >> 12) & 0x3F));
+ a_out[out_index + 4]
+ = (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 4]
+ = (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 6;
+ } else {
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+ } /*end for */
+
+ end:
+ *a_in_len = in_index + 1;
+ *a_out_len = out_index + 1;
+
+ return status;
+}
+
+/**
+ *Converts an ucs4 string into an utf8 string.
+ *@param a_in the input string to convert.
+ *@param a_in_len in/out parameter. The length of the input
+ *string. After return, points to the actual number of characters
+ *consumed. This can be useful to debug the input string in case
+ *of encoding error.
+ *@param a_out out parameter. Points to the output string. It is allocated
+ *by this function and must be freed by the caller.
+ *@param a_out_len out parameter. The length (in bytes) of the output string.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs4_str_to_utf8 (const guint32 * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len && a_out
+ && a_out_len, CR_BAD_PARAM_ERROR);
+
+ status = cr_utils_ucs4_str_len_as_utf8 (a_in,
+ &a_in[*a_out_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ status = cr_utils_ucs4_to_utf8 (a_in, a_in_len, *a_out, a_out_len);
+
+ return status;
+}
+
+/**
+ *Converts an ucs1 buffer into an utf8 buffer.
+ *The caller must know the size of the resulting buffer and
+ *allocate it prior to calling this function.
+ *
+ *@param a_in the input ucs1 buffer.
+ *
+ *@param a_in_len in/out parameter. The length of the input buffer.
+ *After return, points to the number of bytes actually consumed even
+ *in case of encoding error.
+ *
+ *@param a_out out parameter. The output utf8 converted buffer.
+ *
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *If the output buffer size is shorter than the actual needed size,
+ *this function just convert what it can.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *
+ */
+enum CRStatus
+cr_utils_ucs1_to_utf8 (const guchar * a_in,
+ gulong * a_in_len, guchar * a_out, gulong * a_out_len)
+{
+ gulong out_index = 0,
+ in_index = 0,
+ in_len = 0,
+ out_len = 0;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out_len,
+ CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len == 0) {
+ *a_out_len = 0 ;
+ return status;
+ }
+ g_return_val_if_fail (a_out, CR_BAD_PARAM_ERROR) ;
+
+ in_len = *a_in_len;
+ out_len = *a_out_len;
+
+ for (in_index = 0, out_index = 0;
+ (in_index < in_len) && (out_index < out_len); in_index++) {
+ /*
+ *FIXME: return whenever we encounter forbidden char values.
+ */
+
+ if (a_in[in_index] <= 0x7F) {
+ a_out[out_index] = a_in[in_index];
+ out_index++;
+ } else {
+ a_out[out_index] = (0xC0 | (a_in[in_index] >> 6));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 2;
+ }
+ } /*end for */
+
+ *a_in_len = in_index;
+ *a_out_len = out_index;
+
+ return status;
+}
+
+/**
+ *Converts an ucs1 string into an utf8 string.
+ *@param a_in_start the beginning of the input string to convert.
+ *@param a_in_end the end of the input string to convert.
+ *@param a_out out parameter. The converted string.
+ *@param a_out out parameter. The length of the converted string.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *
+ */
+enum CRStatus
+cr_utils_ucs1_str_to_utf8 (const guchar * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ gulong out_len = 0;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len && a_out
+ && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ *a_out_len = 0;
+ *a_out = NULL;
+ return CR_OK;
+ }
+
+ status = cr_utils_ucs1_str_len_as_utf8 (a_in, &a_in[*a_in_len - 1],
+ &out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ *a_out = g_malloc0 (out_len);
+
+ status = cr_utils_ucs1_to_utf8 (a_in, a_in_len, *a_out, &out_len);
+
+ *a_out_len = out_len;
+
+ return status;
+}
+
+/**
+ *Converts an utf8 buffer into an ucs1 buffer.
+ *The caller must know the size of the resulting
+ *converted buffer, and allocated it prior to calling this
+ *function.
+ *
+ *@param a_in the input utf8 buffer to convert.
+ *
+ *@param a_in_len in/out parameter. The size of the input utf8 buffer.
+ *After return, points to the number of bytes consumed
+ *by the function even in case of encoding error.
+ *
+ *@param a_out out parameter. Points to the resulting buffer.
+ *Must be allocated by the caller. If the size of a_out is shorter
+ *than its required size, this function converts what it can and return
+ *a successful status.
+ *
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *After return, points to the number of bytes consumed even in case of
+ *encoding error.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_utf8_to_ucs1 (const guchar * a_in,
+ gulong * a_in_len, guchar * a_out, gulong * a_out_len)
+{
+ gulong in_index = 0,
+ out_index = 0,
+ in_len = 0,
+ out_len = 0;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint32 c = 0;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ goto end;
+ }
+
+ in_len = *a_in_len;
+ out_len = *a_out_len;
+
+ for (in_index = 0, out_index = 0;
+ (in_index < in_len) && (out_index < out_len);
+ in_index++, out_index++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (a_in[in_index] <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = a_in[in_index];
+ nb_bytes_2_decode = 1;
+
+ } else if ((a_in[in_index] & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((a_in[in_index] & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((a_in[in_index] & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((a_in[in_index] & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((a_in[in_index] & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*BAD ENCODING */
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ if (in_index + nb_bytes_2_decode - 1 >= in_len) {
+ goto end;
+ }
+
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ in_index++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((a_in[in_index] & 0xC0) != 0x80) {
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+
+ c = (c << 6) | (a_in[in_index] & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ if (c > 0xFF) {
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+
+ a_out[out_index] = c;
+ }
+
+ end:
+ *a_out_len = out_index;
+ *a_in_len = in_index;
+
+ return status;
+}
+
+/**
+ *Converts an utf8 buffer into an
+ *ucs1 buffer.
+ *@param a_in_start the start of the input buffer.
+ *@param a_in_end the end of the input buffer.
+ *@param a_out out parameter. The resulting converted ucs4 buffer.
+ *Must be freed by the caller.
+ *@param a_out_len out parameter. The length of the converted buffer.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *Note that out parameters are valid if and only if this function
+ *returns CR_OK.
+ */
+enum CRStatus
+cr_utils_utf8_str_to_ucs1 (const guchar * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ *a_out_len = 0;
+ *a_out = NULL;
+ return CR_OK;
+ }
+
+ status = cr_utils_utf8_str_len_as_ucs4 (a_in, &a_in[*a_in_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ *a_out = g_malloc0 (*a_out_len * sizeof (guint32));
+
+ status = cr_utils_utf8_to_ucs1 (a_in, a_in_len, *a_out, a_out_len);
+ return status;
+}
+
+/*****************************************
+ *CSS basic types identification utilities
+ *****************************************/
+
+/**
+ *Returns TRUE if a_char is a white space as
+ *defined in the css spec in chap 4.1.1.
+ *
+ *white-space ::= ' '| \t|\r|\n|\f
+ *
+ *@param a_char the character to test.
+ *return TRUE if is a white space, false otherwise.
+ */
+gboolean
+cr_utils_is_white_space (guint32 a_char)
+{
+ switch (a_char) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ case '\f':
+ return TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ *Returns true if the character is a newline
+ *as defined in the css spec in the chap 4.1.1.
+ *
+ *nl ::= \n|\r\n|\r|\f
+ *
+ *@param a_char the character to test.
+ *@return TRUE if the character is a newline, FALSE otherwise.
+ */
+gboolean
+cr_utils_is_newline (guint32 a_char)
+{
+ switch (a_char) {
+ case '\n':
+ case '\r':
+ case '\f':
+ return TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ *returns TRUE if the char is part of an hexa num char:
+ *i.e hexa_char ::= [0-9A-F]
+ */
+gboolean
+cr_utils_is_hexa_char (guint32 a_char)
+{
+ if ((a_char >= '0' && a_char <= '9')
+ || (a_char >= 'A' && a_char <= 'F')) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ *Returns true if the character is a nonascii
+ *character (as defined in the css spec chap 4.1.1):
+ *
+ *nonascii ::= [^\0-\177]
+ *
+ *@param a_char the character to test.
+ *@return TRUE if the character is a nonascii char,
+ *FALSE otherwise.
+ */
+gboolean
+cr_utils_is_nonascii (guint32 a_char)
+{
+ if (a_char <= 177) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ *Dumps a character a_nb times on a file.
+ *@param a_char the char to dump
+ *@param a_fp the destination file pointer
+ *@param a_nb the number of times a_char is to be dumped.
+ */
+void
+cr_utils_dump_n_chars (guchar a_char, FILE * a_fp, glong a_nb)
+{
+ glong i = 0;
+
+ for (i = 0; i < a_nb; i++) {
+ fprintf (a_fp, "%c", a_char);
+ }
+}
+
+void
+cr_utils_dump_n_chars2 (guchar a_char, GString * a_string, glong a_nb)
+{
+ glong i = 0;
+
+ g_return_if_fail (a_string);
+
+ for (i = 0; i < a_nb; i++) {
+ g_string_append_printf (a_string, "%c", a_char);
+ }
+}
+
+/**
+ *Duplicates a list of GString instances.
+ *@return the duplicated list of GString instances or NULL if
+ *something bad happened.
+ *@param a_list_of_strings the list of strings to be duplicated.
+ */
+GList *
+cr_utils_dup_glist_of_string (GList const * a_list_of_strings)
+{
+ GList const *cur = NULL;
+ GList *result = NULL;
+
+ g_return_val_if_fail (a_list_of_strings, NULL);
+
+ for (cur = a_list_of_strings; cur; cur = cur->next) {
+ GString *str = NULL;
+
+ str = g_string_new_len (((GString *) cur->data)->str,
+ ((GString *) cur->data)->len);
+ if (str)
+ result = g_list_append (result, str);
+ }
+
+ return result;
+}
+
+/**
+ *Duplicate a GList where the GList::data is a CRString.
+ *@param a_list_of_strings the list to duplicate
+ *@return the duplicated list, or NULL if something bad
+ *happened.
+ */
+GList *
+cr_utils_dup_glist_of_cr_string (GList const * a_list_of_strings)
+{
+ GList const *cur = NULL;
+ GList *result = NULL;
+
+ g_return_val_if_fail (a_list_of_strings, NULL);
+
+ for (cur = a_list_of_strings; cur; cur = cur->next) {
+ CRString *str = NULL;
+
+ str = cr_string_dup ((CRString const *) cur->data) ;
+ if (str)
+ result = g_list_append (result, str);
+ }
+
+ return result;
+}
diff --git a/src/st/croco/cr-utils.h b/src/st/croco/cr-utils.h
new file mode 100644
index 0000000..54aa249
--- /dev/null
+++ b/src/st/croco/cr-utils.h
@@ -0,0 +1,246 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * Look at file COPYRIGHTS for copyright information
+ */
+
+#ifndef __CR_DEFS_H__
+#define __CR_DEFS_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "libcroco-config.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The Croco library basic types definitions
+ *And global definitions.
+ */
+
+/**
+ *The status type returned
+ *by the methods of the croco library.
+ */
+enum CRStatus {
+ CR_OK,
+ CR_BAD_PARAM_ERROR,
+ CR_INSTANCIATION_FAILED_ERROR,
+ CR_UNKNOWN_TYPE_ERROR,
+ CR_UNKNOWN_PROP_ERROR,
+ CR_UNKNOWN_PROP_VAL_ERROR,
+ CR_UNEXPECTED_POSITION_SCHEME,
+ CR_START_OF_INPUT_ERROR,
+ CR_END_OF_INPUT_ERROR,
+ CR_OUTPUT_TOO_SHORT_ERROR,
+ CR_INPUT_TOO_SHORT_ERROR,
+ CR_OUT_OF_BOUNDS_ERROR,
+ CR_EMPTY_PARSER_INPUT_ERROR,
+ CR_ENCODING_ERROR,
+ CR_ENCODING_NOT_FOUND_ERROR,
+ CR_PARSING_ERROR,
+ CR_SYNTAX_ERROR,
+ CR_NO_ROOT_NODE_ERROR,
+ CR_NO_TOKEN,
+ CR_OUT_OF_MEMORY_ERROR,
+ CR_PSEUDO_CLASS_SEL_HANDLER_NOT_FOUND_ERROR,
+ CR_BAD_PSEUDO_CLASS_SEL_HANDLER_ERROR,
+ CR_ERROR,
+ CR_FILE_NOT_FOUND_ERROR,
+ CR_VALUE_NOT_FOUND_ERROR
+} ;
+
+/**
+ *Values used by
+ *cr_input_seek_position() ;
+ */
+enum CRSeekPos {
+ CR_SEEK_CUR,
+ CR_SEEK_BEGIN,
+ CR_SEEK_END
+} ;
+
+/**
+ *Encoding values.
+ */
+enum CREncoding
+{
+ CR_UCS_4 = 1/*Must be not NULL*/,
+ CR_UCS_1,
+ CR_ISO_8859_1,
+ CR_ASCII,
+ CR_UTF_8,
+ CR_UTF_16,
+ CR_AUTO/*should be the last one*/
+} ;
+
+
+
+
+#define CROCO_LOG_DOMAIN "LIBCROCO"
+
+#ifdef __GNUC__
+#define cr_utils_trace(a_log_level, a_msg) \
+g_log (CROCO_LOG_DOMAIN, \
+ G_LOG_LEVEL_CRITICAL, \
+ "file %s: line %d (%s): %s\n", \
+ __FILE__, \
+ __LINE__, \
+ __PRETTY_FUNCTION__, \
+ a_msg)
+#else /*__GNUC__*/
+
+#define cr_utils_trace(a_log_level, a_msg) \
+g_log (CROCO_LOG_DOMAIN, \
+ G_LOG_LEVEL_CRITICAL, \
+ "file %s: line %d: %s\n", \
+ __FILE__, \
+ __LINE__, \
+ a_msg)
+#endif
+
+/**
+ *Traces an info message.
+ *The file, line and enclosing function
+ *of the message will be automatically
+ *added to the message.
+ *@param a_msg the msg to trace.
+ */
+#define cr_utils_trace_info(a_msg) \
+cr_utils_trace (G_LOG_LEVEL_INFO, a_msg)
+
+/**
+ *Trace a debug message.
+ *The file, line and enclosing function
+ *of the message will be automatically
+ *added to the message.
+ *@param a_msg the msg to trace.
+ */
+#define cr_utils_trace_debug(a_msg) \
+cr_utils_trace (G_LOG_LEVEL_DEBUG, a_msg) ;
+
+
+/****************************
+ *Encoding transformations and
+ *encoding helpers
+ ****************************/
+
+enum CRStatus
+cr_utils_read_char_from_utf8_buf (const guchar * a_in, gulong a_in_len,
+ guint32 *a_out, gulong *a_consumed) ;
+
+enum CRStatus
+cr_utils_ucs1_to_utf8 (const guchar *a_in, gulong *a_in_len,
+ guchar *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_to_ucs1 (const guchar * a_in, gulong * a_in_len,
+ guchar *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_ucs4_to_utf8 (const guint32 *a_in, gulong *a_in_len,
+ guchar *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs4 (const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_len) ;
+enum CRStatus
+cr_utils_ucs1_str_len_as_utf8 (const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_len) ;
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs1 (const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_len) ;
+enum CRStatus
+cr_utils_ucs4_str_len_as_utf8 (const guint32 *a_in_start,
+ const guint32 *a_in_end,
+ gulong *a_len) ;
+
+enum CRStatus
+cr_utils_ucs1_str_to_utf8 (const guchar *a_in_start,
+ gulong *a_in_len,
+ guchar **a_out,
+ gulong *a_len) ;
+
+enum CRStatus
+cr_utils_utf8_str_to_ucs1 (const guchar * a_in_start,
+ gulong * a_in_len,
+ guchar **a_out,
+ gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_to_ucs4 (const guchar * a_in,
+ gulong * a_in_len,
+ guint32 *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_ucs4_str_to_utf8 (const guint32 *a_in,
+ gulong *a_in_len,
+ guchar **a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_str_to_ucs4 (const guchar * a_in,
+ gulong *a_in_len,
+ guint32 **a_out,
+ gulong *a_out_len) ;
+
+
+/*****************************************
+ *CSS basic types identification utilities
+ *****************************************/
+
+gboolean
+cr_utils_is_newline (guint32 a_char) ;
+
+gboolean
+cr_utils_is_white_space (guint32 a_char) ;
+
+gboolean
+cr_utils_is_nonascii (guint32 a_char) ;
+
+gboolean
+cr_utils_is_hexa_char (guint32 a_char) ;
+
+
+/**********************************
+ *Miscellaneous utility functions
+ ***********************************/
+
+void
+cr_utils_dump_n_chars (guchar a_char,
+ FILE *a_fp,
+ glong a_nb) ;
+
+void
+cr_utils_dump_n_chars2 (guchar a_char,
+ GString *a_string,
+ glong a_nb) ;
+GList *
+cr_utils_dup_glist_of_string (GList const *a_list) ;
+
+GList *
+cr_utils_dup_glist_of_cr_string (GList const * a_list_of_strings) ;
+
+G_END_DECLS
+
+#endif /*__CR_DEFS_H__*/
diff --git a/src/st/croco/libcroco-config.h b/src/st/croco/libcroco-config.h
new file mode 100644
index 0000000..1ffb758
--- /dev/null
+++ b/src/st/croco/libcroco-config.h
@@ -0,0 +1,13 @@
+#ifndef LIBCROCO_VERSION_NUMBER
+#define LIBCROCO_VERSION_NUMBER 612
+#endif
+
+#ifndef LIBCROCO_VERSION
+#define LIBCROCO_VERSION "0.6.12"
+#endif
+
+#ifndef G_DISABLE_CHECKS
+#if 0
+#define G_DISABLE_CHECKS 0
+#endif
+#endif
diff --git a/src/st/croco/libcroco.h b/src/st/croco/libcroco.h
new file mode 100644
index 0000000..6187a7c
--- /dev/null
+++ b/src/st/croco/libcroco.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __LIBCROCO_H__
+#define __LIBCROCO_H__
+
+#include "libcroco-config.h"
+
+#include "cr-utils.h"
+#include "cr-pseudo.h"
+#include "cr-term.h"
+#include "cr-attr-sel.h"
+#include "cr-simple-sel.h"
+#include "cr-selector.h"
+#include "cr-enc-handler.h"
+#include "cr-doc-handler.h"
+#include "cr-input.h"
+#include "cr-parser.h"
+#include "cr-statement.h"
+#include "cr-stylesheet.h"
+#include "cr-om-parser.h"
+#include "cr-prop-list.h"
+#include "cr-string.h"
+
+#endif /*__LIBCROCO_H__*/
diff --git a/src/st/meson.build b/src/st/meson.build
new file mode 100644
index 0000000..717aa05
--- /dev/null
+++ b/src/st/meson.build
@@ -0,0 +1,220 @@
+# please, keep this sorted alphabetically
+st_headers = [
+ 'st-adjustment.h',
+ 'st-bin.h',
+ 'st-border-image.h',
+ 'st-box-layout.h',
+ 'st-button.h',
+ 'st-clipboard.h',
+ 'st-drawing-area.h',
+ 'st-entry.h',
+ 'st-focus-manager.h',
+ 'st-generic-accessible.h',
+ 'st-icon.h',
+ 'st-icon-colors.h',
+ 'st-image-content.h',
+ 'st-label.h',
+ 'st-password-entry.h',
+ 'st-scrollable.h',
+ 'st-scroll-bar.h',
+ 'st-scroll-view.h',
+ 'st-scroll-view-fade.h',
+ 'st-settings.h',
+ 'st-shadow.h',
+ 'st-texture-cache.h',
+ 'st-theme.h',
+ 'st-theme-context.h',
+ 'st-theme-node.h',
+ 'st-types.h',
+ 'st-viewport.h',
+ 'st-widget.h',
+ 'st-widget-accessible.h'
+]
+
+st_includes = []
+foreach include : st_headers
+ st_includes += '#include <@0@>'.format(include)
+endforeach
+
+st_h_data = configuration_data()
+st_h_data.set('includes', '\n'.join(st_includes))
+
+st_h = configure_file(
+ input: 'st.h.in',
+ output: 'st.h',
+ configuration: st_h_data
+)
+
+st_inc = include_directories('.', '..')
+
+# please, keep this sorted alphabetically
+st_private_headers = [
+ 'croco/cr-additional-sel.h',
+ 'croco/cr-attr-sel.h',
+ 'croco/cr-cascade.h',
+ 'croco/cr-declaration.h',
+ 'croco/cr-doc-handler.h',
+ 'croco/cr-enc-handler.h',
+ 'croco/cr-fonts.h',
+ 'croco/cr-input.h',
+ 'croco/cr-num.h',
+ 'croco/cr-om-parser.h',
+ 'croco/cr-parser.h',
+ 'croco/cr-parsing-location.h',
+ 'croco/cr-prop-list.h',
+ 'croco/cr-pseudo.h',
+ 'croco/cr-rgb.h',
+ 'croco/cr-selector.h',
+ 'croco/cr-simple-sel.h',
+ 'croco/cr-statement.h',
+ 'croco/cr-string.h',
+ 'croco/cr-stylesheet.h',
+ 'croco/cr-term.h',
+ 'croco/cr-tknzr.h',
+ 'croco/cr-token.h',
+ 'croco/cr-utils.h',
+ 'croco/libcroco-config.h',
+ 'croco/libcroco.h',
+ 'st-private.h',
+ 'st-theme-private.h',
+ 'st-theme-node-private.h',
+ 'st-theme-node-transition.h'
+]
+
+# please, keep this sorted alphabetically
+croco_sources = [
+ 'croco/cr-additional-sel.c',
+ 'croco/cr-attr-sel.c',
+ 'croco/cr-cascade.c',
+ 'croco/cr-declaration.c',
+ 'croco/cr-doc-handler.c',
+ 'croco/cr-enc-handler.c',
+ 'croco/cr-fonts.c',
+ 'croco/cr-input.c',
+ 'croco/cr-num.c',
+ 'croco/cr-om-parser.c',
+ 'croco/cr-parser.c',
+ 'croco/cr-parsing-location.c',
+ 'croco/cr-prop-list.c',
+ 'croco/cr-pseudo.c',
+ 'croco/cr-rgb.c',
+ 'croco/cr-selector.c',
+ 'croco/cr-simple-sel.c',
+ 'croco/cr-statement.c',
+ 'croco/cr-string.c',
+ 'croco/cr-stylesheet.c',
+ 'croco/cr-term.c',
+ 'croco/cr-tknzr.c',
+ 'croco/cr-token.c',
+ 'croco/cr-utils.c',
+]
+
+# please, keep this sorted alphabetically
+st_sources = [
+ 'st-adjustment.c',
+ 'st-bin.c',
+ 'st-border-image.c',
+ 'st-box-layout.c',
+ 'st-button.c',
+ 'st-clipboard.c',
+ 'st-drawing-area.c',
+ 'st-entry.c',
+ 'st-focus-manager.c',
+ 'st-generic-accessible.c',
+ 'st-icon.c',
+ 'st-icon-colors.c',
+ 'st-image-content.c',
+ 'st-label.c',
+ 'st-password-entry.c',
+ 'st-private.c',
+ 'st-scrollable.c',
+ 'st-scroll-bar.c',
+ 'st-scroll-view.c',
+ 'st-scroll-view-fade.c',
+ 'st-settings.c',
+ 'st-shadow.c',
+ 'st-texture-cache.c',
+ 'st-theme.c',
+ 'st-theme-context.c',
+ 'st-theme-node.c',
+ 'st-theme-node-drawing.c',
+ 'st-theme-node-transition.c',
+ 'st-viewport.c',
+ 'st-widget.c'
+]
+
+st_enums = gnome.mkenums_simple('st-enum-types',
+ sources: st_headers,
+ header_prefix: '''
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif'''
+)
+
+st_gir_sources = st_sources + st_headers + st_enums
+
+data_to_c = find_program(meson.project_source_root() + '/src/data-to-c.pl')
+
+glsl_sources = custom_target('scroll-view-fade-glsl',
+ input: ['st-scroll-view-fade.glsl'],
+ output: ['st-scroll-view-fade-generated.h'],
+ capture: true,
+ command: [data_to_c, '@INPUT@', 'st_scroll_view_fade_glsl']
+)
+
+st_nogir_sources = [glsl_sources]
+
+st_cflags = [
+ '-I@0@/src'.format(meson.project_source_root()),
+ '-I@0@'.format(meson.project_build_root()),
+ '-DPREFIX="@0@"'.format(prefix),
+ '-DLIBDIR="@0@"'.format(libdir),
+ '-DG_LOG_DOMAIN="St"',
+ '-DST_COMPILATION',
+ '-DCLUTTER_ENABLE_EXPERIMENTAL_API',
+ '-DCOGL_ENABLE_EXPERIMENTAL_API',
+ '-DPACKAGE_DATA_DIR="@0@"'.format(pkgdatadir)
+]
+
+# Currently meson requires a shared library for building girs
+libst = shared_library('st-1.0',
+ sources: st_gir_sources + st_nogir_sources + croco_sources,
+ c_args: st_cflags,
+ dependencies: [clutter_dep, gtk_dep, mutter_dep, libxml_dep, m_dep],
+ build_rpath: mutter_typelibdir,
+ install_rpath: mutter_typelibdir,
+ install_dir: pkglibdir,
+ install: true
+)
+
+libst_dep = declare_dependency(link_with: libst,
+ sources: st_enums[1]
+)
+
+if get_option('tests')
+ mutter_test_dep = dependency(libmutter_test_pc, version: mutter_req)
+ test_theme = executable('test-theme',
+ sources: 'test-theme.c',
+ c_args: st_cflags,
+ dependencies: [mutter_test_dep, gtk_dep, libxml_dep],
+ build_rpath: mutter_typelibdir,
+ link_with: libst
+ )
+
+ test('CSS styling support', test_theme,
+ workdir: meson.current_source_dir()
+ )
+endif
+
+libst_gir = gnome.generate_gir(libst,
+ sources: st_gir_sources,
+ nsversion: '1.0',
+ namespace: 'St',
+ includes: ['Clutter-' + mutter_api_version, 'Cally-' + mutter_api_version, 'Meta-' + mutter_api_version, 'Gtk-3.0'],
+ dependencies: [mutter_dep],
+ include_directories: include_directories('..'),
+ extra_args: ['-DST_COMPILATION', '--quiet'],
+ install_dir_gir: pkgdatadir,
+ install_dir_typelib: pkglibdir,
+ install: true
+)
diff --git a/src/st/st-adjustment.c b/src/st/st-adjustment.c
new file mode 100644
index 0000000..d2baa66
--- /dev/null
+++ b/src/st/st-adjustment.c
@@ -0,0 +1,1013 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-adjustment.c: Adjustment object
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-adjustment
+ * @short_description: A GObject representing an adjustable bounded value
+ *
+ * The #StAdjustment object represents a range of values bounded between a
+ * minimum and maximum, together with step and page increments and a page size.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <math.h>
+
+#include "st-adjustment.h"
+#include "st-private.h"
+
+typedef struct _StAdjustmentPrivate StAdjustmentPrivate;
+
+struct _StAdjustmentPrivate
+{
+ ClutterActor *actor;
+
+ /* Do not sanity-check values while constructing,
+ * not all properties may be set yet. */
+ guint is_constructing : 1;
+
+ GHashTable *transitions;
+
+ gdouble lower;
+ gdouble upper;
+ gdouble value;
+ gdouble step_increment;
+ gdouble page_increment;
+ gdouble page_size;
+};
+
+static void animatable_iface_init (ClutterAnimatableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StAdjustment, st_adjustment, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (StAdjustment)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_ANIMATABLE,
+ animatable_iface_init));
+
+enum
+{
+ PROP_0,
+
+ PROP_ACTOR,
+ PROP_LOWER,
+ PROP_UPPER,
+ PROP_VALUE,
+ PROP_STEP_INC,
+ PROP_PAGE_INC,
+ PROP_PAGE_SIZE,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct _TransitionClosure
+{
+ StAdjustment *adjustment;
+ ClutterTransition *transition;
+ char *name;
+ gulong completed_id;
+} TransitionClosure;
+
+static gboolean st_adjustment_set_lower (StAdjustment *adjustment,
+ gdouble lower);
+static gboolean st_adjustment_set_upper (StAdjustment *adjustment,
+ gdouble upper);
+static gboolean st_adjustment_set_step_increment (StAdjustment *adjustment,
+ gdouble step);
+static gboolean st_adjustment_set_page_increment (StAdjustment *adjustment,
+ gdouble page);
+static gboolean st_adjustment_set_page_size (StAdjustment *adjustment,
+ gdouble size);
+
+static ClutterActor *
+st_adjustment_get_actor (ClutterAnimatable *animatable)
+{
+ StAdjustment *adjustment = ST_ADJUSTMENT (animatable);
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ g_warn_if_fail (priv->actor);
+
+ return priv->actor;
+}
+
+static void
+animatable_iface_init (ClutterAnimatableInterface *iface)
+{
+ iface->get_actor = st_adjustment_get_actor;
+}
+
+static void
+st_adjustment_constructed (GObject *object)
+{
+ GObjectClass *g_class;
+ StAdjustment *self = ST_ADJUSTMENT (object);
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (self);
+
+ g_class = G_OBJECT_CLASS (st_adjustment_parent_class);
+ /* The docs say we're suppose to chain up, but would crash without
+ * some extra care. */
+ if (g_class && g_class->constructed &&
+ g_class->constructed != st_adjustment_constructed)
+ {
+ g_class->constructed (object);
+ }
+
+ priv->is_constructing = FALSE;
+ st_adjustment_clamp_page (self, priv->lower, priv->upper);
+}
+
+static void
+st_adjustment_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (ST_ADJUSTMENT (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_ACTOR:
+ g_value_set_object (value, priv->actor);
+ break;
+
+ case PROP_LOWER:
+ g_value_set_double (value, priv->lower);
+ break;
+
+ case PROP_UPPER:
+ g_value_set_double (value, priv->upper);
+ break;
+
+ case PROP_VALUE:
+ g_value_set_double (value, priv->value);
+ break;
+
+ case PROP_STEP_INC:
+ g_value_set_double (value, priv->step_increment);
+ break;
+
+ case PROP_PAGE_INC:
+ g_value_set_double (value, priv->page_increment);
+ break;
+
+ case PROP_PAGE_SIZE:
+ g_value_set_double (value, priv->page_size);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+actor_destroyed (gpointer user_data,
+ GObject *where_the_object_was)
+{
+ StAdjustment *adj = ST_ADJUSTMENT (user_data);
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adj);
+
+ priv->actor = NULL;
+
+ g_object_notify_by_pspec (G_OBJECT (adj), props[PROP_ACTOR]);
+}
+
+static void
+st_adjustment_set_actor (StAdjustment *adj,
+ ClutterActor *actor)
+{
+ StAdjustmentPrivate *priv;
+
+ priv = st_adjustment_get_instance_private (adj);
+
+ if (priv->actor == actor)
+ return;
+
+ if (priv->actor)
+ g_object_weak_unref (G_OBJECT (priv->actor), actor_destroyed, adj);
+ priv->actor = actor;
+ if (priv->actor)
+ g_object_weak_ref (G_OBJECT (priv->actor), actor_destroyed, adj);
+
+ g_object_notify_by_pspec (G_OBJECT (adj), props[PROP_ACTOR]);
+}
+
+static void
+st_adjustment_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StAdjustment *adj = ST_ADJUSTMENT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_ACTOR:
+ st_adjustment_set_actor (adj, g_value_get_object (value));
+ break;
+
+ case PROP_LOWER:
+ st_adjustment_set_lower (adj, g_value_get_double (value));
+ break;
+
+ case PROP_UPPER:
+ st_adjustment_set_upper (adj, g_value_get_double (value));
+ break;
+
+ case PROP_VALUE:
+ st_adjustment_set_value (adj, g_value_get_double (value));
+ break;
+
+ case PROP_STEP_INC:
+ st_adjustment_set_step_increment (adj, g_value_get_double (value));
+ break;
+
+ case PROP_PAGE_INC:
+ st_adjustment_set_page_increment (adj, g_value_get_double (value));
+ break;
+
+ case PROP_PAGE_SIZE:
+ st_adjustment_set_page_size (adj, g_value_get_double (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_adjustment_dispose (GObject *object)
+{
+ StAdjustmentPrivate *priv;
+
+ priv = st_adjustment_get_instance_private (ST_ADJUSTMENT (object));
+ if (priv->actor)
+ {
+ g_object_weak_unref (G_OBJECT (priv->actor), actor_destroyed, object);
+ priv->actor = NULL;
+ }
+ g_clear_pointer (&priv->transitions, g_hash_table_unref);
+
+ G_OBJECT_CLASS (st_adjustment_parent_class)->dispose (object);
+}
+
+static void
+st_adjustment_class_init (StAdjustmentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = st_adjustment_constructed;
+ object_class->get_property = st_adjustment_get_property;
+ object_class->set_property = st_adjustment_set_property;
+ object_class->dispose = st_adjustment_dispose;
+
+ /**
+ * StAdjustment:actor:
+ *
+ * If the adjustment is used as #ClutterAnimatable for a
+ * #ClutterPropertyTransition, this property is used to determine which
+ * monitor should drive the animation.
+ */
+ props[PROP_ACTOR] =
+ g_param_spec_object ("actor", "Actor", "Actor",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:lower:
+ *
+ * The minimum value of the adjustment.
+ */
+ props[PROP_LOWER] =
+ g_param_spec_double ("lower", "Lower", "Lower bound",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:upper:
+ *
+ * The maximum value of the adjustment.
+ *
+ * Note that values will be restricted by `upper - page-size` if
+ * #StAdjustment:page-size is non-zero.
+ */
+ props[PROP_UPPER] =
+ g_param_spec_double ("upper", "Upper", "Upper bound",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:value:
+ *
+ * The value of the adjustment.
+ */
+ props[PROP_VALUE] =
+ g_param_spec_double ("value", "Value", "Current value",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:step-increment:
+ *
+ * The step increment of the adjustment.
+ */
+ props[PROP_STEP_INC] =
+ g_param_spec_double ("step-increment", "Step Increment", "Step increment",
+ 0.0, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:page-increment:
+ *
+ * The page increment of the adjustment.
+ */
+ props[PROP_PAGE_INC] =
+ g_param_spec_double ("page-increment", "Page Increment", "Page increment",
+ 0.0, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:page-size:
+ *
+ * The page size of the adjustment.
+ *
+ * Note that the page-size is irrelevant and should be set to zero if the
+ * adjustment is used for a simple scalar value.
+ */
+ props[PROP_PAGE_SIZE] =
+ g_param_spec_double ("page-size", "Page Size", "Page size",
+ 0.0, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+ /**
+ * StAdjustment::changed:
+ * @self: the #StAdjustment
+ *
+ * Emitted when any of the adjustment properties have changed, except for
+ * #StAdjustment:value.
+ */
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StAdjustmentClass, changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_adjustment_init (StAdjustment *self)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (self);
+ priv->is_constructing = TRUE;
+}
+
+/**
+ * st_adjustment_new:
+ * @actor: (nullable): a #ClutterActor
+ * @value: the initial value
+ * @lower: the minimum value
+ * @upper: the maximum value
+ * @step_increment: the step increment
+ * @page_increment: the page increment
+ * @page_size: the page size
+ *
+ * Creates a new #StAdjustment
+ *
+ * Returns: a new #StAdjustment
+ */
+StAdjustment *
+st_adjustment_new (ClutterActor *actor,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size)
+{
+ return g_object_new (ST_TYPE_ADJUSTMENT,
+ "actor", actor,
+ "value", value,
+ "lower", lower,
+ "upper", upper,
+ "step-increment", step_increment,
+ "page-increment", page_increment,
+ "page-size", page_size,
+ NULL);
+}
+
+/**
+ * st_adjustment_get_value:
+ * @adjustment: a #StAdjustment
+ *
+ * Gets the current value of the adjustment. See st_adjustment_set_value().
+ *
+ * Returns: The current value of the adjustment
+ */
+gdouble
+st_adjustment_get_value (StAdjustment *adjustment)
+{
+ g_return_val_if_fail (ST_IS_ADJUSTMENT (adjustment), 0);
+
+ return ((StAdjustmentPrivate *)st_adjustment_get_instance_private (adjustment))->value;
+}
+
+/**
+ * st_adjustment_set_value:
+ * @adjustment: a #StAdjustment
+ * @value: the new value
+ *
+ * Sets the #StAdjustment value. The value is clamped to lie between
+ * #StAdjustment:lower and #StAdjustment:upper - #StAdjustment:page-size.
+ */
+void
+st_adjustment_set_value (StAdjustment *adjustment,
+ gdouble value)
+{
+ StAdjustmentPrivate *priv;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ /* Defer clamp until after construction. */
+ if (!priv->is_constructing)
+ {
+ value = CLAMP (value,
+ priv->lower,
+ MAX (priv->lower, priv->upper - priv->page_size));
+ }
+
+ if (priv->value != value)
+ {
+ priv->value = value;
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_VALUE]);
+ }
+}
+
+/**
+ * st_adjustment_clamp_page:
+ * @adjustment: a #StAdjustment
+ * @lower: the lower value
+ * @upper: the upper value
+ *
+ * Set #StAdjustment:value to a value clamped between @lower and @upper. The
+ * clamping described by st_adjustment_set_value() still applies.
+ */
+void
+st_adjustment_clamp_page (StAdjustment *adjustment,
+ gdouble lower,
+ gdouble upper)
+{
+ StAdjustmentPrivate *priv;
+ gboolean changed;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ lower = CLAMP (lower, priv->lower, priv->upper - priv->page_size);
+ upper = CLAMP (upper, priv->lower + priv->page_size, priv->upper);
+
+ changed = FALSE;
+
+ if (priv->value + priv->page_size > upper)
+ {
+ priv->value = upper - priv->page_size;
+ changed = TRUE;
+ }
+
+ if (priv->value < lower)
+ {
+ priv->value = lower;
+ changed = TRUE;
+ }
+
+ if (changed)
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_VALUE]);
+}
+
+/**
+ * st_adjustment_set_lower:
+ * @adjustment: a #StAdjustment
+ * @lower: the new minimum value
+ *
+ * Sets the minimum value of the adjustment.
+ *
+ * When setting multiple adjustment properties via their individual
+ * setters, multiple #GObject::notify and #StAdjustment::changed
+ * signals will be emitted. However, it’s possible to compress the
+ * #GObject::notify signals into one by calling
+ * g_object_freeze_notify() and g_object_thaw_notify() around the
+ * calls to the individual setters.
+ *
+ * Alternatively, using st_adjustment_set_values() will compress both
+ * #GObject::notify and #StAdjustment::changed emissions.
+ */
+static gboolean
+st_adjustment_set_lower (StAdjustment *adjustment,
+ gdouble lower)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->lower != lower)
+ {
+ priv->lower = lower;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_LOWER]);
+
+ /* Defer clamp until after construction. */
+ if (!priv->is_constructing)
+ st_adjustment_clamp_page (adjustment, priv->lower, priv->upper);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_upper:
+ * @adjustment: a #StAdjustment
+ * @upper: the new maximum value
+ *
+ * Sets the maximum value of the adjustment.
+ *
+ * Note that values will be restricted by `upper - page-size`
+ * if the page-size property is nonzero.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_upper (StAdjustment *adjustment,
+ gdouble upper)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->upper != upper)
+ {
+ priv->upper = upper;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_UPPER]);
+
+ /* Defer clamp until after construction. */
+ if (!priv->is_constructing)
+ st_adjustment_clamp_page (adjustment, priv->lower, priv->upper);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_step_increment:
+ * @adjustment: a #StAdjustment
+ * @step: the new step increment
+ *
+ * Sets the step increment of the adjustment.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_step_increment (StAdjustment *adjustment,
+ gdouble step)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->step_increment != step)
+ {
+ priv->step_increment = step;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_STEP_INC]);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_page_increment:
+ * @adjustment: a #StAdjustment
+ * @page: the new page increment
+ *
+ * Sets the page increment of the adjustment.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_page_increment (StAdjustment *adjustment,
+ gdouble page)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->page_increment != page)
+ {
+ priv->page_increment = page;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_PAGE_INC]);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_page_size:
+ * @adjustment: a #StAdjustment
+ * @size: the new page size
+ *
+ * Sets the page size of the adjustment.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_page_size (StAdjustment *adjustment,
+ gdouble size)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->page_size != size)
+ {
+ priv->page_size = size;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_PAGE_SIZE]);
+
+ /* We'll explicitly clamp after construction. */
+ if (!priv->is_constructing)
+ st_adjustment_clamp_page (adjustment, priv->lower, priv->upper);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_values:
+ * @adjustment: a #StAdjustment
+ * @value: the new value
+ * @lower: the new minimum value
+ * @upper: the new maximum value
+ * @step_increment: the new step increment
+ * @page_increment: the new page increment
+ * @page_size: the new page size
+ *
+ * Sets all properties of the adjustment at once.
+ *
+ * Use this function to avoid multiple emissions of the #GObject::notify and
+ * #StAdjustment::changed signals. See st_adjustment_set_lower() for an
+ * alternative way of compressing multiple emissions of #GObject::notify into
+ * one.
+ */
+void
+st_adjustment_set_values (StAdjustment *adjustment,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size)
+{
+ StAdjustmentPrivate *priv;
+ gboolean emit_changed = FALSE;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+ g_return_if_fail (page_size >= 0 && page_size <= G_MAXDOUBLE);
+ g_return_if_fail (step_increment >= 0 && step_increment <= G_MAXDOUBLE);
+ g_return_if_fail (page_increment >= 0 && page_increment <= G_MAXDOUBLE);
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ emit_changed = FALSE;
+
+ g_object_freeze_notify (G_OBJECT (adjustment));
+
+ emit_changed |= st_adjustment_set_lower (adjustment, lower);
+ emit_changed |= st_adjustment_set_upper (adjustment, upper);
+ emit_changed |= st_adjustment_set_step_increment (adjustment, step_increment);
+ emit_changed |= st_adjustment_set_page_increment (adjustment, page_increment);
+ emit_changed |= st_adjustment_set_page_size (adjustment, page_size);
+
+ if (value != priv->value)
+ {
+ st_adjustment_set_value (adjustment, value);
+ emit_changed = TRUE;
+ }
+
+ if (emit_changed)
+ g_signal_emit (G_OBJECT (adjustment), signals[CHANGED], 0);
+
+ g_object_thaw_notify (G_OBJECT (adjustment));
+}
+
+/**
+ * st_adjustment_get_values:
+ * @adjustment: an #StAdjustment
+ * @value: (out) (optional): the current value
+ * @lower: (out) (optional): the lower bound
+ * @upper: (out) (optional): the upper bound
+ * @step_increment: (out) (optional): the step increment
+ * @page_increment: (out) (optional): the page increment
+ * @page_size: (out) (optional): the page size
+ *
+ * Gets all of @adjustment's values at once.
+ */
+void
+st_adjustment_get_values (StAdjustment *adjustment,
+ gdouble *value,
+ gdouble *lower,
+ gdouble *upper,
+ gdouble *step_increment,
+ gdouble *page_increment,
+ gdouble *page_size)
+{
+ StAdjustmentPrivate *priv;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (lower)
+ *lower = priv->lower;
+
+ if (upper)
+ *upper = priv->upper;
+
+ if (value)
+ *value = st_adjustment_get_value (adjustment);
+
+ if (step_increment)
+ *step_increment = priv->step_increment;
+
+ if (page_increment)
+ *page_increment = priv->page_increment;
+
+ if (page_size)
+ *page_size = priv->page_size;
+}
+
+/**
+ * st_adjustment_adjust_for_scroll_event:
+ * @adjustment: An #StAdjustment
+ * @delta: A delta, retrieved directly from clutter_event_get_scroll_delta()
+ * or similar.
+ *
+ * Adjusts the adjustment using delta values from a scroll event.
+ * You should use this instead of using st_adjustment_set_value()
+ * as this method will tweak the values directly using the same
+ * math as GTK+, to ensure that scrolling is consistent across
+ * the environment.
+ */
+void
+st_adjustment_adjust_for_scroll_event (StAdjustment *adjustment,
+ gdouble delta)
+{
+ StAdjustmentPrivate *priv;
+ gdouble new_value, scroll_unit;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ scroll_unit = pow (priv->page_size, 2.0 / 3.0);
+
+ new_value = priv->value + delta * scroll_unit;
+ st_adjustment_set_value (adjustment, new_value);
+}
+
+static void
+transition_closure_free (gpointer data)
+{
+ TransitionClosure *clos;
+ ClutterTimeline *timeline;
+
+ if (G_UNLIKELY (data == NULL))
+ return;
+
+ clos = data;
+ timeline = CLUTTER_TIMELINE (clos->transition);
+
+ g_clear_signal_handler (&clos->completed_id, clos->transition);
+
+ if (clutter_timeline_is_playing (timeline))
+ clutter_timeline_stop (timeline);
+
+ g_object_unref (clos->transition);
+ g_free (clos->name);
+ g_free (clos);
+}
+
+static void
+remove_transition (StAdjustment *adjustment,
+ const char *name)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ g_hash_table_remove (priv->transitions, name);
+
+ if (g_hash_table_size (priv->transitions) == 0)
+ g_clear_pointer (&priv->transitions, g_hash_table_unref);
+}
+
+static void
+on_transition_stopped (ClutterTransition *transition,
+ gboolean is_finished,
+ TransitionClosure *clos)
+{
+ StAdjustment *adjustment = clos->adjustment;
+
+ if (!clutter_transition_get_remove_on_complete (transition))
+ return;
+
+ /* Take a reference, because removing the closure will
+ * release the reference on the transition, and we want
+ * it to survive the signal emission; ClutterTransition's
+ * own ::stopped signal closure will release it after all
+ * other handlers have run.
+ */
+ g_object_ref (transition);
+
+ remove_transition (adjustment, clos->name);
+}
+
+/**
+ * st_adjustment_get_transition:
+ * @adjustment: a #StAdjustment
+ * @name: a transition name
+ *
+ * Get the #ClutterTransition for @name previously added with
+ * st_adjustment_add_transition() or %NULL if not found.
+ *
+ * Returns: (transfer none) (nullable): a #ClutterTransition
+ */
+ClutterTransition *
+st_adjustment_get_transition (StAdjustment *adjustment,
+ const char *name)
+{
+ StAdjustmentPrivate *priv;
+ TransitionClosure *clos;
+
+ g_return_val_if_fail (ST_IS_ADJUSTMENT (adjustment), NULL);
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->transitions == NULL)
+ return NULL;
+
+ clos = g_hash_table_lookup (priv->transitions, name);
+ if (clos == NULL)
+ return NULL;
+
+ return clos->transition;
+}
+
+/**
+ * st_adjustment_add_transition:
+ * @adjustment: a #StAdjustment
+ * @name: a unique name for the transition
+ * @transition: a #ClutterTransition
+ *
+ * Add a #ClutterTransition for the adjustment. If the transition stops, it will
+ * be automatically removed if #ClutterTransition:remove-on-complete is %TRUE.
+ */
+void
+st_adjustment_add_transition (StAdjustment *adjustment,
+ const char *name,
+ ClutterTransition *transition)
+{
+ StAdjustmentPrivate *priv;
+ TransitionClosure *clos;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (CLUTTER_IS_TRANSITION (transition));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->transitions == NULL)
+ priv->transitions = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL,
+ transition_closure_free);
+
+ if (g_hash_table_lookup (priv->transitions, name) != NULL)
+ {
+ g_warning ("A transition with name '%s' already exists for "
+ "adjustment '%p'", name, adjustment);
+ return;
+ }
+
+ clutter_transition_set_animatable (transition, CLUTTER_ANIMATABLE (adjustment));
+
+ clos = g_new (TransitionClosure, 1);
+ clos->adjustment = adjustment;
+ clos->transition = g_object_ref (transition);
+ clos->name = g_strdup (name);
+ clos->completed_id = g_signal_connect (transition, "stopped",
+ G_CALLBACK (on_transition_stopped),
+ clos);
+
+ g_hash_table_insert (priv->transitions, clos->name, clos);
+ clutter_timeline_start (CLUTTER_TIMELINE (transition));
+}
+
+/**
+ * st_adjusmtent_remove_transition:
+ * @adjustment: a #StAdjustment
+ * @name: the name of the transition to remove
+ *
+ * Remove a #ClutterTransition previously added by st_adjustment_add_transtion()
+ * with @name.
+ */
+void
+st_adjustment_remove_transition (StAdjustment *adjustment,
+ const char *name)
+{
+ StAdjustmentPrivate *priv;
+ TransitionClosure *clos;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+ g_return_if_fail (name != NULL);
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->transitions == NULL)
+ return;
+
+ clos = g_hash_table_lookup (priv->transitions, name);
+ if (clos == NULL)
+ return;
+
+ remove_transition (adjustment, name);
+}
diff --git a/src/st/st-adjustment.h b/src/st/st-adjustment.h
new file mode 100644
index 0000000..08a0fc3
--- /dev/null
+++ b/src/st/st-adjustment.h
@@ -0,0 +1,92 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-adjustment.h: Adjustment object
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_ADJUSTMENT_H__
+#define __ST_ADJUSTMENT_H__
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_ADJUSTMENT (st_adjustment_get_type())
+G_DECLARE_DERIVABLE_TYPE (StAdjustment, st_adjustment, ST, ADJUSTMENT, GObject)
+
+/**
+ * StAdjustmentClass:
+ * @changed: Class handler for the ::changed signal.
+ *
+ * Base class for #StAdjustment.
+ */
+struct _StAdjustmentClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ void (* changed) (StAdjustment *adjustment);
+};
+
+StAdjustment *st_adjustment_new (ClutterActor *actor,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size);
+gdouble st_adjustment_get_value (StAdjustment *adjustment);
+void st_adjustment_set_value (StAdjustment *adjustment,
+ gdouble value);
+void st_adjustment_clamp_page (StAdjustment *adjustment,
+ gdouble lower,
+ gdouble upper);
+void st_adjustment_set_values (StAdjustment *adjustment,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size);
+void st_adjustment_get_values (StAdjustment *adjustment,
+ gdouble *value,
+ gdouble *lower,
+ gdouble *upper,
+ gdouble *step_increment,
+ gdouble *page_increment,
+ gdouble *page_size);
+
+void st_adjustment_adjust_for_scroll_event (StAdjustment *adjustment,
+ gdouble delta);
+
+ClutterTransition * st_adjustment_get_transition (StAdjustment *adjustment,
+ const char *name);
+void st_adjustment_add_transition (StAdjustment *adjustment,
+ const char *name,
+ ClutterTransition *transition);
+void st_adjustment_remove_transition (StAdjustment *adjustment,
+ const char *name);
+
+G_END_DECLS
+
+#endif /* __ST_ADJUSTMENT_H__ */
diff --git a/src/st/st-bin.c b/src/st/st-bin.c
new file mode 100644
index 0000000..9d86ea2
--- /dev/null
+++ b/src/st/st-bin.c
@@ -0,0 +1,404 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-bin.c: Basic container actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-bin
+ * @short_description: a simple container with one actor
+ *
+ * #StBin is a simple container capable of having only one
+ * #ClutterActor as a child.
+ *
+ * #StBin inherits from #StWidget, so it is fully themable.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <clutter/clutter.h>
+
+#include "st-bin.h"
+#include "st-enum-types.h"
+#include "st-private.h"
+
+typedef struct _StBinPrivate StBinPrivate;
+struct _StBinPrivate
+{
+ ClutterActor *child;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CHILD,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static void clutter_container_iface_init (ClutterContainerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StBin, st_bin, ST_TYPE_WIDGET,
+ G_ADD_PRIVATE (StBin)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
+ clutter_container_iface_init));
+
+static void
+st_bin_add (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ st_bin_set_child (ST_BIN (container), actor);
+}
+
+static void
+st_bin_remove (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StBin *bin = ST_BIN (container);
+ StBinPrivate *priv = st_bin_get_instance_private (bin);
+
+ if (priv->child == actor)
+ st_bin_set_child (bin, NULL);
+}
+
+static void
+clutter_container_iface_init (ClutterContainerIface *iface)
+{
+ iface->add = st_bin_add;
+ iface->remove = st_bin_remove;
+}
+
+static double
+get_align_factor (ClutterActorAlign align)
+{
+ switch (align)
+ {
+ case CLUTTER_ACTOR_ALIGN_CENTER:
+ return 0.5;
+
+ case CLUTTER_ACTOR_ALIGN_START:
+ return 0.0;
+
+ case CLUTTER_ACTOR_ALIGN_END:
+ return 1.0;
+
+ case CLUTTER_ACTOR_ALIGN_FILL:
+ break;
+ }
+
+ return 0.0;
+}
+
+static void
+st_bin_allocate (ClutterActor *self,
+ const ClutterActorBox *box)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (self));
+
+ clutter_actor_set_allocation (self, box);
+
+ if (priv->child && clutter_actor_is_visible (priv->child))
+ {
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ ClutterActorAlign x_align = clutter_actor_get_x_align (priv->child);
+ ClutterActorAlign y_align = clutter_actor_get_y_align (priv->child);
+ ClutterActorBox childbox;
+
+ st_theme_node_get_content_box (theme_node, box, &childbox);
+ clutter_actor_allocate_align_fill (priv->child, &childbox,
+ get_align_factor (x_align),
+ get_align_factor (y_align),
+ x_align == CLUTTER_ACTOR_ALIGN_FILL,
+ y_align == CLUTTER_ACTOR_ALIGN_FILL);
+ }
+}
+
+static void
+st_bin_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (self));
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ if (priv->child == NULL || !clutter_actor_is_visible (priv->child))
+ {
+ if (min_width_p)
+ *min_width_p = 0;
+
+ if (natural_width_p)
+ *natural_width_p = 0;
+ }
+ else
+ {
+ ClutterActorAlign y_align = clutter_actor_get_y_align (priv->child);
+
+ _st_actor_get_preferred_width (priv->child, for_height,
+ y_align == CLUTTER_ACTOR_ALIGN_FILL,
+ min_width_p,
+ natural_width_p);
+ }
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_bin_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (self));
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ if (priv->child == NULL || !clutter_actor_is_visible (priv->child))
+ {
+ if (min_height_p)
+ *min_height_p = 0;
+
+ if (natural_height_p)
+ *natural_height_p = 0;
+ }
+ else
+ {
+ ClutterActorAlign x_align = clutter_actor_get_y_align (priv->child);
+
+ _st_actor_get_preferred_height (priv->child, for_width,
+ x_align == CLUTTER_ACTOR_ALIGN_FILL,
+ min_height_p,
+ natural_height_p);
+ }
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_bin_destroy (ClutterActor *actor)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (actor));
+
+ if (priv->child)
+ clutter_actor_destroy (priv->child);
+ g_assert (priv->child == NULL);
+
+ CLUTTER_ACTOR_CLASS (st_bin_parent_class)->destroy (actor);
+}
+
+static void
+st_bin_popup_menu (StWidget *widget)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (widget));
+
+ if (priv->child && ST_IS_WIDGET (priv->child))
+ st_widget_popup_menu (ST_WIDGET (priv->child));
+}
+
+static gboolean
+st_bin_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (widget));
+ ClutterActor *bin_actor = CLUTTER_ACTOR (widget);
+
+ if (st_widget_get_can_focus (widget))
+ {
+ if (from && clutter_actor_contains (bin_actor, from))
+ return FALSE;
+
+ if (clutter_actor_is_mapped (bin_actor))
+ {
+ clutter_actor_grab_key_focus (bin_actor);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+ else if (priv->child && ST_IS_WIDGET (priv->child))
+ return st_widget_navigate_focus (ST_WIDGET (priv->child), from, direction, FALSE);
+ else
+ return FALSE;
+}
+
+static void
+st_bin_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StBin *bin = ST_BIN (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD:
+ st_bin_set_child (bin, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+st_bin_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_CHILD:
+ g_value_set_object (value, priv->child);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+st_bin_class_init (StBinClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_bin_set_property;
+ gobject_class->get_property = st_bin_get_property;
+
+ actor_class->get_preferred_width = st_bin_get_preferred_width;
+ actor_class->get_preferred_height = st_bin_get_preferred_height;
+ actor_class->allocate = st_bin_allocate;
+ actor_class->destroy = st_bin_destroy;
+
+ widget_class->popup_menu = st_bin_popup_menu;
+ widget_class->navigate_focus = st_bin_navigate_focus;
+
+ /**
+ * StBin:child:
+ *
+ * The child #ClutterActor of the #StBin container.
+ */
+ props[PROP_CHILD] =
+ g_param_spec_object ("child",
+ "Child",
+ "The child of the Bin",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+st_bin_init (StBin *bin)
+{
+}
+
+/**
+ * st_bin_new:
+ *
+ * Creates a new #StBin, a simple container for one child.
+ *
+ * Returns: the newly created #StBin actor
+ */
+StWidget *
+st_bin_new (void)
+{
+ return g_object_new (ST_TYPE_BIN, NULL);
+}
+
+/**
+ * st_bin_set_child:
+ * @bin: a #StBin
+ * @child: (nullable): a #ClutterActor, or %NULL
+ *
+ * Sets @child as the child of @bin.
+ *
+ * If @bin already has a child, the previous child is removed.
+ */
+void
+st_bin_set_child (StBin *bin,
+ ClutterActor *child)
+{
+ StBinPrivate *priv;
+
+ g_return_if_fail (ST_IS_BIN (bin));
+ g_return_if_fail (child == NULL || CLUTTER_IS_ACTOR (child));
+
+ priv = st_bin_get_instance_private (bin);
+
+ if (priv->child == child)
+ return;
+
+ if (child)
+ {
+ ClutterActor *parent = clutter_actor_get_parent (child);
+
+ if (parent)
+ {
+ g_warning ("%s: The provided 'child' actor %p already has a "
+ "(different) parent %p and can't be made a child of %p.",
+ G_STRFUNC, child, parent, bin);
+ return;
+ }
+ }
+
+ if (priv->child)
+ clutter_actor_remove_child (CLUTTER_ACTOR (bin), priv->child);
+
+ priv->child = NULL;
+
+ if (child)
+ {
+ priv->child = child;
+ clutter_actor_add_child (CLUTTER_ACTOR (bin), child);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (bin));
+
+ g_object_notify_by_pspec (G_OBJECT (bin), props[PROP_CHILD]);
+}
+
+/**
+ * st_bin_get_child:
+ * @bin: a #StBin
+ *
+ * Gets the #ClutterActor child for @bin.
+ *
+ * Returns: (transfer none) (nullable): a #ClutterActor, or %NULL
+ */
+ClutterActor *
+st_bin_get_child (StBin *bin)
+{
+ g_return_val_if_fail (ST_IS_BIN (bin), NULL);
+
+ return ((StBinPrivate *)st_bin_get_instance_private (bin))->child;
+}
diff --git a/src/st/st-bin.h b/src/st/st-bin.h
new file mode 100644
index 0000000..7784f45
--- /dev/null
+++ b/src/st/st-bin.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-bin.h: Basic container actor
+ *
+ * Copyright 2009, 2008 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_BIN_H__
+#define __ST_BIN_H__
+
+#include <st/st-types.h>
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_BIN (st_bin_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StBin, st_bin, ST, BIN, StWidget)
+
+/**
+ * StBinClass:
+ *
+ * The #StBinClass struct contains only private data
+ */
+struct _StBinClass
+{
+ /*< private >*/
+ StWidgetClass parent_class;
+};
+
+StWidget * st_bin_new (void);
+void st_bin_set_child (StBin *bin,
+ ClutterActor *child);
+ClutterActor *st_bin_get_child (StBin *bin);
+
+G_END_DECLS
+
+#endif /* __ST_BIN_H__ */
diff --git a/src/st/st-border-image.c b/src/st/st-border-image.c
new file mode 100644
index 0000000..ee09d02
--- /dev/null
+++ b/src/st/st-border-image.c
@@ -0,0 +1,171 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-border-image.c: store information about an image with borders
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include "st-border-image.h"
+
+struct _StBorderImage {
+ GObject parent;
+
+ GFile *file;
+ int border_top;
+ int border_right;
+ int border_bottom;
+ int border_left;
+
+ int scale_factor;
+};
+
+struct _StBorderImageClass {
+ GObjectClass parent_class;
+
+};
+
+G_DEFINE_TYPE (StBorderImage, st_border_image, G_TYPE_OBJECT)
+
+static void
+st_border_image_finalize (GObject *object)
+{
+ StBorderImage *image = ST_BORDER_IMAGE (object);
+
+ g_object_unref (image->file);
+
+ G_OBJECT_CLASS (st_border_image_parent_class)->finalize (object);
+}
+
+static void
+st_border_image_class_init (StBorderImageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = st_border_image_finalize;
+}
+
+static void
+st_border_image_init (StBorderImage *image)
+{
+}
+
+/**
+ * st_border_image_new:
+ * @file: a #GFile
+ * @border_top: the top border
+ * @border_right: the right border
+ * @border_bottom: the bottom border
+ * @border_left: the left border
+ * @scale_factor: the scale factor
+ *
+ * Creates a new #StBorderImage.
+ *
+ * Returns: a new #StBorderImage.
+ */
+StBorderImage *
+st_border_image_new (GFile *file,
+ int border_top,
+ int border_right,
+ int border_bottom,
+ int border_left,
+ int scale_factor)
+{
+ StBorderImage *image;
+
+ image = g_object_new (ST_TYPE_BORDER_IMAGE, NULL);
+
+ image->file = g_object_ref (file);
+ image->border_top = border_top;
+ image->border_right = border_right;
+ image->border_bottom = border_bottom;
+ image->border_left = border_left;
+ image->scale_factor = scale_factor;
+
+ return image;
+}
+
+/**
+ * st_border_image_get_file:
+ * @image: a #StBorderImage
+ *
+ * Get the #GFile for @image.
+ *
+ * Returns: (transfer none): a #GFile
+ */
+GFile *
+st_border_image_get_file (StBorderImage *image)
+{
+ g_return_val_if_fail (ST_IS_BORDER_IMAGE (image), NULL);
+
+ return image->file;
+}
+
+/**
+ * st_border_image_get_border:
+ * @image: a #StBorderImage
+ * @border_top: (out) (optional): the top border
+ * @border_right: (out) (optional): the right border
+ * @border_bottom: (out) (optional): the bottom border
+ * @border_left: (out) (optional): the left border
+ *
+ * Get the border widths for @image, taking into account the scale factor
+ * provided at construction.
+ */
+void
+st_border_image_get_borders (StBorderImage *image,
+ int *border_top,
+ int *border_right,
+ int *border_bottom,
+ int *border_left)
+{
+ g_return_if_fail (ST_IS_BORDER_IMAGE (image));
+
+ if (border_top)
+ *border_top = image->border_top * image->scale_factor;
+ if (border_right)
+ *border_right = image->border_right * image->scale_factor;
+ if (border_bottom)
+ *border_bottom = image->border_bottom * image->scale_factor;
+ if (border_left)
+ *border_left = image->border_left * image->scale_factor;
+}
+
+/**
+ * st_border_image_equal:
+ * @image: a #StBorderImage
+ * @other: a different #StBorderImage
+ *
+ * Check if two #StBorderImage objects are identical.
+ *
+ * Returns: %TRUE if the two border image objects are identical
+ */
+gboolean
+st_border_image_equal (StBorderImage *image,
+ StBorderImage *other)
+{
+ g_return_val_if_fail (ST_IS_BORDER_IMAGE (image), FALSE);
+ g_return_val_if_fail (ST_IS_BORDER_IMAGE (other), FALSE);
+
+ return (image->border_top == other->border_top &&
+ image->border_right == other->border_right &&
+ image->border_bottom == other->border_bottom &&
+ image->border_left == other->border_left &&
+ g_file_equal (image->file, other->file));
+}
diff --git a/src/st/st-border-image.h b/src/st/st-border-image.h
new file mode 100644
index 0000000..9c58152
--- /dev/null
+++ b/src/st/st-border-image.h
@@ -0,0 +1,54 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-border-image.h: store information about an image with borders
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_BORDER_IMAGE_H__
+#define __ST_BORDER_IMAGE_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/* A StBorderImage encapsulates an image with specified unscaled borders on each edge.
+ */
+
+#define ST_TYPE_BORDER_IMAGE (st_border_image_get_type ())
+G_DECLARE_FINAL_TYPE (StBorderImage, st_border_image, ST, BORDER_IMAGE, GObject)
+
+StBorderImage *st_border_image_new (GFile *file,
+ int border_top,
+ int border_right,
+ int border_bottom,
+ int border_left,
+ int scale_factor);
+
+GFile *st_border_image_get_file (StBorderImage *image);
+void st_border_image_get_borders (StBorderImage *image,
+ int *border_top,
+ int *border_right,
+ int *border_bottom,
+ int *border_left);
+
+gboolean st_border_image_equal (StBorderImage *image,
+ StBorderImage *other);
+
+G_END_DECLS
+
+#endif /* __ST_BORDER_IMAGE_H__ */
diff --git a/src/st/st-box-layout.c b/src/st/st-box-layout.c
new file mode 100644
index 0000000..990fb9e
--- /dev/null
+++ b/src/st/st-box-layout.c
@@ -0,0 +1,307 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-box-layout.h: box layout actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Muellner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Portions copied from Clutter:
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Matthew Allum <mallum@openedhand.com>
+ *
+ * Copyright (C) 2006 OpenedHand
+ * 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.
+ */
+
+/**
+ * SECTION:st-box-layout
+ * @short_description: a layout container arranging children in a single line
+ *
+ * The #StBoxLayout arranges its children along a single line, where each
+ * child can be allocated either its preferred size or larger if the expand
+ * option is set. If the fill option is set, the actor will be allocated more
+ * than its requested size. If the fill option is not set, but the expand option
+ * is enabled, then the position of the actor within the available space can
+ * be determined by the alignment child property.
+ *
+ */
+
+#include <stdlib.h>
+
+#include "st-box-layout.h"
+
+#include "st-private.h"
+#include "st-scrollable.h"
+
+
+enum {
+ PROP_0,
+
+ PROP_VERTICAL,
+ PROP_PACK_START,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+struct _StBoxLayoutPrivate
+{
+ StAdjustment *hadjustment;
+ StAdjustment *vadjustment;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StBoxLayout, st_box_layout, ST_TYPE_VIEWPORT);
+
+
+static void
+st_box_layout_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterLayoutManager *layout;
+ ClutterOrientation orientation;
+
+ switch (property_id)
+ {
+ case PROP_VERTICAL:
+ layout = clutter_actor_get_layout_manager (CLUTTER_ACTOR (object));
+ orientation = clutter_box_layout_get_orientation (CLUTTER_BOX_LAYOUT (layout));
+ g_value_set_boolean (value, orientation == CLUTTER_ORIENTATION_VERTICAL);
+ break;
+
+ case PROP_PACK_START:
+ g_value_set_boolean (value, FALSE);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_box_layout_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StBoxLayout *box = ST_BOX_LAYOUT (object);
+
+ switch (property_id)
+ {
+ case PROP_VERTICAL:
+ st_box_layout_set_vertical (box, g_value_get_boolean (value));
+ break;
+
+ case PROP_PACK_START:
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_box_layout_style_changed (StWidget *self)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (self);
+ ClutterBoxLayout *layout;
+ double spacing;
+
+ layout = CLUTTER_BOX_LAYOUT (clutter_actor_get_layout_manager (CLUTTER_ACTOR (self)));
+
+ spacing = st_theme_node_get_length (theme_node, "spacing");
+ clutter_box_layout_set_spacing (layout, (int)(spacing + 0.5));
+
+ ST_WIDGET_CLASS (st_box_layout_parent_class)->style_changed (self);
+}
+
+static void
+layout_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GObject *self = user_data;
+ const char *prop_name = g_param_spec_get_name (pspec);
+
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (self), prop_name))
+ g_object_notify (self, prop_name);
+}
+
+static void
+on_layout_manager_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ClutterActor *actor = CLUTTER_ACTOR (object);
+ ClutterLayoutManager *layout = clutter_actor_get_layout_manager (actor);
+
+ g_warn_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+
+ if (layout == NULL)
+ return;
+
+ g_signal_connect_swapped (layout, "layout-changed",
+ G_CALLBACK (clutter_actor_queue_relayout), actor);
+ g_signal_connect (layout, "notify", G_CALLBACK (layout_notify), object);
+}
+
+static void
+st_box_layout_class_init (StBoxLayoutClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ object_class->get_property = st_box_layout_get_property;
+ object_class->set_property = st_box_layout_set_property;
+
+ widget_class->style_changed = st_box_layout_style_changed;
+
+ /**
+ * StBoxLayout:vertical:
+ *
+ * A convenience property for the #ClutterBoxLayout:vertical property of the
+ * internal layout for #StBoxLayout.
+ */
+ props[PROP_VERTICAL] =
+ g_param_spec_boolean ("vertical",
+ "Vertical",
+ "Whether the layout should be vertical, rather"
+ "than horizontal",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StBoxLayout:pack-start:
+ *
+ * A convenience property for the #ClutterBoxLayout:pack-start property of the
+ * internal layout for #StBoxLayout.
+ */
+ props[PROP_PACK_START] =
+ g_param_spec_boolean ("pack-start",
+ "Pack Start",
+ "Whether to pack items at the start of the box",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_DEPRECATED);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_box_layout_init (StBoxLayout *self)
+{
+ self->priv = st_box_layout_get_instance_private (self);
+
+ g_signal_connect (self, "notify::layout-manager",
+ G_CALLBACK (on_layout_manager_notify), NULL);
+ clutter_actor_set_layout_manager (CLUTTER_ACTOR (self), clutter_box_layout_new ());
+}
+
+/**
+ * st_box_layout_new:
+ *
+ * Create a new #StBoxLayout.
+ *
+ * Returns: a newly allocated #StBoxLayout
+ */
+StWidget *
+st_box_layout_new (void)
+{
+ return g_object_new (ST_TYPE_BOX_LAYOUT, NULL);
+}
+
+/**
+ * st_box_layout_set_vertical:
+ * @box: A #StBoxLayout
+ * @vertical: %TRUE if the layout should be vertical
+ *
+ * Set the value of the #StBoxLayout:vertical property
+ */
+void
+st_box_layout_set_vertical (StBoxLayout *box,
+ gboolean vertical)
+{
+ ClutterLayoutManager *layout;
+ ClutterOrientation orientation;
+
+ g_return_if_fail (ST_IS_BOX_LAYOUT (box));
+
+ layout = clutter_actor_get_layout_manager (CLUTTER_ACTOR (box));
+ orientation = vertical ? CLUTTER_ORIENTATION_VERTICAL
+ : CLUTTER_ORIENTATION_HORIZONTAL;
+
+ if (clutter_box_layout_get_orientation (CLUTTER_BOX_LAYOUT (layout)) != orientation)
+ {
+ clutter_box_layout_set_orientation (CLUTTER_BOX_LAYOUT (layout), orientation);
+ g_object_notify_by_pspec (G_OBJECT (box), props[PROP_VERTICAL]);
+ }
+}
+
+/**
+ * st_box_layout_get_vertical:
+ * @box: A #StBoxLayout
+ *
+ * Get the value of the #StBoxLayout:vertical property.
+ *
+ * Returns: %TRUE if the layout is vertical
+ */
+gboolean
+st_box_layout_get_vertical (StBoxLayout *box)
+{
+ ClutterLayoutManager *layout;
+ ClutterOrientation orientation;
+
+ g_return_val_if_fail (ST_IS_BOX_LAYOUT (box), FALSE);
+
+ layout = clutter_actor_get_layout_manager (CLUTTER_ACTOR (box));
+ orientation = clutter_box_layout_get_orientation (CLUTTER_BOX_LAYOUT (layout));
+ return orientation == CLUTTER_ORIENTATION_VERTICAL;
+}
+
+/**
+ * st_box_layout_set_pack_start:
+ * @box: A #StBoxLayout
+ * @pack_start: %TRUE if the layout should use pack-start
+ *
+ * Deprecated: No longer has any effect
+ */
+void
+st_box_layout_set_pack_start (StBoxLayout *box,
+ gboolean pack_start)
+{
+}
+
+/**
+ * st_box_layout_get_pack_start:
+ * @box: A #StBoxLayout
+ *
+ * Returns: the value of the #StBoxLayout:pack-start property,
+ * always %FALSE
+ */
+gboolean
+st_box_layout_get_pack_start (StBoxLayout *box)
+{
+ return FALSE;
+}
diff --git a/src/st/st-box-layout.h b/src/st/st-box-layout.h
new file mode 100644
index 0000000..82f5a70
--- /dev/null
+++ b/src/st/st-box-layout.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-box-layout.h: box layout actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef _ST_BOX_LAYOUT_H
+#define _ST_BOX_LAYOUT_H
+
+#include <st/st-widget.h>
+#include <st/st-viewport.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_BOX_LAYOUT st_box_layout_get_type()
+G_DECLARE_FINAL_TYPE (StBoxLayout, st_box_layout, ST, BOX_LAYOUT, StViewport)
+
+typedef struct _StBoxLayout StBoxLayout;
+typedef struct _StBoxLayoutPrivate StBoxLayoutPrivate;
+
+/**
+ * StBoxLayout:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StBoxLayout
+{
+ /*< private >*/
+ StViewport parent;
+
+ StBoxLayoutPrivate *priv;
+};
+
+StWidget *st_box_layout_new (void);
+
+void st_box_layout_set_vertical (StBoxLayout *box,
+ gboolean vertical);
+gboolean st_box_layout_get_vertical (StBoxLayout *box);
+
+void st_box_layout_set_pack_start (StBoxLayout *box,
+ gboolean pack_start);
+gboolean st_box_layout_get_pack_start (StBoxLayout *box);
+
+G_END_DECLS
+
+#endif /* _ST_BOX_LAYOUT_H */
diff --git a/src/st/st-button.c b/src/st/st-button.c
new file mode 100644
index 0000000..148197c
--- /dev/null
+++ b/src/st/st-button.c
@@ -0,0 +1,1043 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-button.c: Plain button actor
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-button
+ * @short_description: Button widget
+ *
+ * A button widget with support for either a text label or icon, toggle mode
+ * and transitions effects between states.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+#include "st-button.h"
+
+#include "st-icon.h"
+#include "st-enum-types.h"
+#include "st-texture-cache.h"
+#include "st-private.h"
+
+#include <st/st-widget-accessible.h>
+
+enum
+{
+ PROP_0,
+
+ PROP_LABEL,
+ PROP_ICON_NAME,
+ PROP_BUTTON_MASK,
+ PROP_TOGGLE_MODE,
+ PROP_CHECKED,
+ PROP_PRESSED,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ CLICKED,
+
+ LAST_SIGNAL
+};
+
+typedef struct _StButtonPrivate StButtonPrivate;
+
+struct _StButtonPrivate
+{
+ gchar *text;
+
+ ClutterInputDevice *device;
+ ClutterEventSequence *press_sequence;
+ ClutterGrab *grab;
+
+ guint button_mask : 3;
+ guint is_toggle : 1;
+
+ guint pressed : 3;
+ guint grabbed : 3;
+ guint is_checked : 1;
+};
+
+static guint button_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StButton, st_button, ST_TYPE_BIN);
+
+static GType st_button_accessible_get_type (void) G_GNUC_CONST;
+
+static void
+st_button_update_label_style (StButton *button)
+{
+ ClutterActor *label;
+
+ label = st_bin_get_child (ST_BIN (button));
+
+ /* check the child is really a label */
+ if (!CLUTTER_IS_TEXT (label))
+ return;
+
+ _st_set_text_from_style (CLUTTER_TEXT (label), st_widget_get_theme_node (ST_WIDGET (button)));
+}
+
+static void
+st_button_style_changed (StWidget *widget)
+{
+ StButton *button = ST_BUTTON (widget);
+ StButtonClass *button_class = ST_BUTTON_GET_CLASS (button);
+
+ ST_WIDGET_CLASS (st_button_parent_class)->style_changed (widget);
+
+ /* update the label styling */
+ st_button_update_label_style (button);
+
+ /* run a transition if applicable */
+ if (button_class->transition)
+ {
+ button_class->transition (button);
+ }
+}
+
+static void
+st_button_press (StButton *button,
+ ClutterInputDevice *device,
+ StButtonMask mask,
+ ClutterEventSequence *sequence)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ gboolean active_changed = priv->pressed == 0 || sequence;
+
+ if (active_changed)
+ st_widget_add_style_pseudo_class (ST_WIDGET (button), "active");
+
+ priv->pressed |= mask;
+ priv->press_sequence = sequence;
+ priv->device = device;
+
+ if (active_changed)
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_PRESSED]);
+}
+
+static void
+st_button_release (StButton *button,
+ ClutterInputDevice *device,
+ StButtonMask mask,
+ int clicked_button,
+ ClutterEventSequence *sequence)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ if ((device && priv->device != device) ||
+ (sequence && priv->press_sequence != sequence))
+ return;
+ else if (!sequence)
+ {
+ priv->pressed &= ~mask;
+
+ if (priv->pressed != 0)
+ return;
+ }
+
+ priv->press_sequence = NULL;
+ priv->device = NULL;
+ st_widget_remove_style_pseudo_class (ST_WIDGET (button), "active");
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_PRESSED]);
+
+ if (clicked_button || sequence)
+ {
+ if (priv->is_toggle)
+ st_button_set_checked (button, !priv->is_checked);
+
+ g_signal_emit (button, button_signals[CLICKED], 0, clicked_button);
+ }
+}
+
+static gboolean
+st_button_button_press (ClutterActor *actor,
+ ClutterButtonEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ StButtonMask mask = ST_BUTTON_MASK_FROM_BUTTON (event->button);
+ ClutterInputDevice *device = clutter_event_get_device ((ClutterEvent*) event);
+
+ if (priv->press_sequence)
+ return CLUTTER_EVENT_PROPAGATE;
+
+ if (priv->button_mask & mask)
+ {
+ ClutterActor *stage;
+
+ stage = clutter_actor_get_stage (actor);
+
+ if (priv->grabbed == 0)
+ priv->grab = clutter_stage_grab (CLUTTER_STAGE (stage), actor);
+
+ priv->grabbed |= mask;
+ st_button_press (button, device, mask, NULL);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_button_button_release (ClutterActor *actor,
+ ClutterButtonEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ StButtonMask mask = ST_BUTTON_MASK_FROM_BUTTON (event->button);
+ ClutterInputDevice *device = clutter_event_get_device ((ClutterEvent*) event);
+
+ if (priv->button_mask & mask)
+ {
+ ClutterStage *stage;
+ ClutterActor *target;
+ gboolean is_click;
+
+ stage = clutter_event_get_stage ((ClutterEvent *) event);
+ target = clutter_stage_get_event_actor (stage, (ClutterEvent *) event);
+
+ is_click = priv->grabbed && clutter_actor_contains (actor, target);
+ st_button_release (button, device, mask, is_click ? event->button : 0, NULL);
+
+ priv->grabbed &= ~mask;
+ if (priv->grab && priv->grabbed == 0)
+ {
+ clutter_grab_dismiss (priv->grab);
+ g_clear_pointer (&priv->grab, clutter_grab_unref);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_button_touch_event (ClutterActor *actor,
+ ClutterTouchEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ StButtonMask mask = ST_BUTTON_MASK_FROM_BUTTON (1);
+ ClutterEventSequence *sequence;
+ ClutterInputDevice *device;
+
+ if (priv->pressed != 0)
+ return CLUTTER_EVENT_PROPAGATE;
+ if ((priv->button_mask & mask) == 0)
+ return CLUTTER_EVENT_PROPAGATE;
+
+ device = clutter_event_get_device ((ClutterEvent*) event);
+ sequence = clutter_event_get_event_sequence ((ClutterEvent*) event);
+
+ if (event->type == CLUTTER_TOUCH_BEGIN && !priv->grab && !priv->press_sequence)
+ {
+ st_button_press (button, device, 0, sequence);
+ return CLUTTER_EVENT_STOP;
+ }
+ else if (event->type == CLUTTER_TOUCH_END &&
+ priv->device == device &&
+ priv->press_sequence == sequence)
+ {
+ st_button_release (button, device, mask, 0, sequence);
+ return CLUTTER_EVENT_STOP;
+ }
+ else if (event->type == CLUTTER_TOUCH_CANCEL)
+ {
+ st_button_fake_release (button);
+ }
+
+ return CLUTTER_EVENT_PROPAGATE;
+}
+
+static gboolean
+st_button_key_press (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ if (priv->button_mask & ST_BUTTON_ONE)
+ {
+ if (event->keyval == CLUTTER_KEY_space ||
+ event->keyval == CLUTTER_KEY_Return ||
+ event->keyval == CLUTTER_KEY_KP_Enter ||
+ event->keyval == CLUTTER_KEY_ISO_Enter)
+ {
+ st_button_press (button, NULL, ST_BUTTON_ONE, NULL);
+ return TRUE;
+ }
+ }
+
+ return CLUTTER_ACTOR_CLASS (st_button_parent_class)->key_press_event (actor, event);
+}
+
+static gboolean
+st_button_key_release (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ if (priv->button_mask & ST_BUTTON_ONE)
+ {
+ if (event->keyval == CLUTTER_KEY_space ||
+ event->keyval == CLUTTER_KEY_Return ||
+ event->keyval == CLUTTER_KEY_KP_Enter ||
+ event->keyval == CLUTTER_KEY_ISO_Enter)
+ {
+ gboolean is_click;
+
+ is_click = (priv->pressed & ST_BUTTON_ONE);
+ st_button_release (button, NULL, ST_BUTTON_ONE, is_click ? 1 : 0, NULL);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+st_button_key_focus_out (ClutterActor *actor)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ /* If we lose focus between a key press and release, undo the press */
+ if ((priv->pressed & ST_BUTTON_ONE) &&
+ !(priv->grabbed & ST_BUTTON_ONE))
+ st_button_release (button, NULL, ST_BUTTON_ONE, 0, NULL);
+
+ CLUTTER_ACTOR_CLASS (st_button_parent_class)->key_focus_out (actor);
+}
+
+static gboolean
+st_button_enter (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ gboolean ret;
+
+ ret = CLUTTER_ACTOR_CLASS (st_button_parent_class)->enter_event (actor, event);
+
+ if (priv->grabbed)
+ {
+ if (st_widget_get_hover (ST_WIDGET (button)))
+ st_button_press (button, priv->device,
+ priv->grabbed, NULL);
+ else
+ st_button_release (button, priv->device,
+ priv->grabbed, 0, NULL);
+ }
+
+ return ret;
+}
+
+static gboolean
+st_button_leave (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ gboolean ret;
+
+ ret = CLUTTER_ACTOR_CLASS (st_button_parent_class)->leave_event (actor, event);
+
+ if (priv->grabbed)
+ {
+ if (st_widget_get_hover (ST_WIDGET (button)))
+ st_button_press (button, priv->device,
+ priv->grabbed, NULL);
+ else
+ st_button_release (button, priv->device,
+ priv->grabbed, 0, NULL);
+ }
+
+ return ret;
+}
+
+static void
+st_button_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StButton *button = ST_BUTTON (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ st_button_set_label (button, g_value_get_string (value));
+ break;
+ case PROP_ICON_NAME:
+ st_button_set_icon_name (button, g_value_get_string (value));
+ break;
+ case PROP_BUTTON_MASK:
+ st_button_set_button_mask (button, g_value_get_flags (value));
+ break;
+ case PROP_TOGGLE_MODE:
+ st_button_set_toggle_mode (button, g_value_get_boolean (value));
+ break;
+ case PROP_CHECKED:
+ st_button_set_checked (button, g_value_get_boolean (value));
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_button_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (ST_BUTTON (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ g_value_set_string (value, priv->text);
+ break;
+ case PROP_ICON_NAME:
+ g_value_set_string (value, st_button_get_icon_name (ST_BUTTON (gobject)));
+ break;
+ case PROP_BUTTON_MASK:
+ g_value_set_flags (value, priv->button_mask);
+ break;
+ case PROP_TOGGLE_MODE:
+ g_value_set_boolean (value, priv->is_toggle);
+ break;
+ case PROP_CHECKED:
+ g_value_set_boolean (value, priv->is_checked);
+ break;
+ case PROP_PRESSED:
+ g_value_set_boolean (value, priv->pressed != 0 || priv->press_sequence != NULL);
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_button_finalize (GObject *gobject)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (ST_BUTTON (gobject));
+
+ g_free (priv->text);
+
+ G_OBJECT_CLASS (st_button_parent_class)->finalize (gobject);
+}
+
+static void
+st_button_class_init (StButtonClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_button_set_property;
+ gobject_class->get_property = st_button_get_property;
+ gobject_class->finalize = st_button_finalize;
+
+ actor_class->button_press_event = st_button_button_press;
+ actor_class->button_release_event = st_button_button_release;
+ actor_class->key_press_event = st_button_key_press;
+ actor_class->key_release_event = st_button_key_release;
+ actor_class->key_focus_out = st_button_key_focus_out;
+ actor_class->enter_event = st_button_enter;
+ actor_class->leave_event = st_button_leave;
+ actor_class->touch_event = st_button_touch_event;
+
+ widget_class->style_changed = st_button_style_changed;
+ widget_class->get_accessible_type = st_button_accessible_get_type;
+
+ /**
+ * StButton:label:
+ *
+ * The label of the #StButton.
+ */
+ props[PROP_LABEL] =
+ g_param_spec_string ("label",
+ "Label",
+ "Label of the button",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:icon-name:
+ *
+ * The icon name of the #StButton.
+ */
+ props[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon name",
+ "Icon name of the button",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:button-mask:
+ *
+ * Which buttons will trigger the #StButton::clicked signal.
+ */
+ props[PROP_BUTTON_MASK] =
+ g_param_spec_flags ("button-mask",
+ "Button mask",
+ "Which buttons trigger the 'clicked' signal",
+ ST_TYPE_BUTTON_MASK, ST_BUTTON_ONE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:toggle-mode:
+ *
+ * Whether the #StButton is operating in toggle mode (on/off).
+ */
+ props[PROP_TOGGLE_MODE] =
+ g_param_spec_boolean ("toggle-mode",
+ "Toggle Mode",
+ "Enable or disable toggling",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:checked:
+ *
+ * If #StButton:toggle-mode is %TRUE, indicates if the #StButton is toggled
+ * "on" or "off".
+ *
+ * When the value is %TRUE, the #StButton will have the `checked` CSS
+ * pseudo-class set.
+ */
+ props[PROP_CHECKED] =
+ g_param_spec_boolean ("checked",
+ "Checked",
+ "Indicates if a toggle button is \"on\" or \"off\"",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:pressed:
+ *
+ * In contrast to #StButton:checked, this property indicates whether the
+ * #StButton is being actively pressed, rather than just in the "on" state.
+ */
+ props[PROP_PRESSED] =
+ g_param_spec_boolean ("pressed",
+ "Pressed",
+ "Indicates if the button is pressed in",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+
+ /**
+ * StButton::clicked:
+ * @button: the object that received the signal
+ * @clicked_button: the mouse button that was used
+ *
+ * Emitted when the user activates the button, either with a mouse press and
+ * release or with the keyboard.
+ */
+ button_signals[CLICKED] =
+ g_signal_new ("clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StButtonClass, clicked),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+}
+
+static void
+st_button_init (StButton *button)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ priv->button_mask = ST_BUTTON_ONE;
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (button), TRUE);
+ st_widget_set_track_hover (ST_WIDGET (button), TRUE);
+}
+
+/**
+ * st_button_new:
+ *
+ * Create a new button
+ *
+ * Returns: a new #StButton
+ */
+StWidget *
+st_button_new (void)
+{
+ return g_object_new (ST_TYPE_BUTTON, NULL);
+}
+
+/**
+ * st_button_new_with_label:
+ * @text: text to set the label to
+ *
+ * Create a new #StButton with the specified label
+ *
+ * Returns: a new #StButton
+ */
+StWidget *
+st_button_new_with_label (const gchar *text)
+{
+ return g_object_new (ST_TYPE_BUTTON, "label", text, NULL);
+}
+
+/**
+ * st_button_get_label:
+ * @button: a #StButton
+ *
+ * Get the text displayed on the button. If the label is empty, an empty string
+ * will be returned instead of %NULL.
+ *
+ * Returns: (transfer none): the text for the button
+ */
+const gchar *
+st_button_get_label (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), NULL);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->text;
+}
+
+/**
+ * st_button_set_label:
+ * @button: a #Stbutton
+ * @text: (nullable): text to set the label to
+ *
+ * Sets the text displayed on the button.
+ */
+void
+st_button_set_label (StButton *button,
+ const gchar *text)
+{
+ StButtonPrivate *priv;
+ ClutterActor *label;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (g_strcmp0 (priv->text, text) == 0)
+ return;
+
+ g_free (priv->text);
+
+ if (text)
+ priv->text = g_strdup (text);
+ else
+ priv->text = g_strdup ("");
+
+ label = st_bin_get_child (ST_BIN (button));
+
+ if (label && CLUTTER_IS_TEXT (label))
+ {
+ clutter_text_set_text (CLUTTER_TEXT (label), priv->text);
+ }
+ else
+ {
+ label = g_object_new (CLUTTER_TYPE_TEXT,
+ "text", priv->text,
+ "line-alignment", PANGO_ALIGN_CENTER,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "use-markup", TRUE,
+ "x-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ "y-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ NULL);
+ st_bin_set_child (ST_BIN (button), label);
+ }
+
+ /* Fake a style change so that we reset the style properties on the label */
+ st_widget_style_changed (ST_WIDGET (button));
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_LABEL]);
+}
+
+/**
+ * st_button_get_icon_name:
+ * @button: a #StButton
+ *
+ * Get the icon name of the button. If the button isn't showing an icon,
+ * the return value will be %NULL.
+ *
+ * Returns: (transfer none) (nullable): the icon name of the button
+ */
+const char *
+st_button_get_icon_name (StButton *button)
+{
+ ClutterActor *icon;
+
+ g_return_val_if_fail (ST_IS_BUTTON (button), NULL);
+
+ icon = st_bin_get_child (ST_BIN (button));
+ if (ST_IS_ICON (icon))
+ return st_icon_get_icon_name (ST_ICON (icon));
+ return NULL;
+}
+
+/**
+ * st_button_set_icon_name:
+ * @button: a #Stbutton
+ * @icon_name: an icon name
+ *
+ * Adds an `StIcon` with the given icon name as a child.
+ *
+ * If @button already contains a child actor, that child will
+ * be removed and replaced with the icon.
+ */
+void
+st_button_set_icon_name (StButton *button,
+ const char *icon_name)
+{
+ ClutterActor *icon;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+ g_return_if_fail (icon_name != NULL);
+
+ icon = st_bin_get_child (ST_BIN (button));
+
+ if (ST_IS_ICON (icon))
+ {
+ if (g_strcmp0 (st_icon_get_icon_name (ST_ICON (icon)), icon_name) == 0)
+ return;
+
+ st_icon_set_icon_name (ST_ICON (icon), icon_name);
+ }
+ else
+ {
+ icon = g_object_new (ST_TYPE_ICON,
+ "icon-name", icon_name,
+ "x-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ "y-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ NULL);
+ st_bin_set_child (ST_BIN (button), icon);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ICON_NAME]);
+}
+
+/**
+ * st_button_get_button_mask:
+ * @button: a #StButton
+ *
+ * Gets the mask of mouse buttons that @button emits the
+ * #StButton::clicked signal for.
+ *
+ * Returns: the mask of mouse buttons that @button emits the
+ * #StButton::clicked signal for.
+ */
+StButtonMask
+st_button_get_button_mask (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), 0);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->button_mask;
+}
+
+/**
+ * st_button_set_button_mask:
+ * @button: a #Stbutton
+ * @mask: the mask of mouse buttons that @button responds to
+ *
+ * Sets which mouse buttons @button emits #StButton::clicked for.
+ */
+void
+st_button_set_button_mask (StButton *button,
+ StButtonMask mask)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (priv->button_mask == mask)
+ return;
+
+ priv->button_mask = mask;
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_BUTTON_MASK]);
+}
+
+/**
+ * st_button_get_toggle_mode:
+ * @button: a #StButton
+ *
+ * Get the toggle mode status of the button.
+ *
+ * Returns: %TRUE if toggle mode is set, otherwise %FALSE
+ */
+gboolean
+st_button_get_toggle_mode (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), FALSE);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->is_toggle;
+}
+
+/**
+ * st_button_set_toggle_mode:
+ * @button: a #Stbutton
+ * @toggle: %TRUE or %FALSE
+ *
+ * Enables or disables toggle mode for the button. In toggle mode, the active
+ * state will be "toggled" when the user clicks the button.
+ */
+void
+st_button_set_toggle_mode (StButton *button,
+ gboolean toggle)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (priv->is_toggle == toggle)
+ return;
+
+ priv->is_toggle = toggle;
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_TOGGLE_MODE]);
+}
+
+/**
+ * st_button_get_checked:
+ * @button: a #StButton
+ *
+ * Get the #StButton:checked property of a #StButton that is in toggle mode.
+ *
+ * Returns: %TRUE if the button is checked, or %FALSE if not
+ */
+gboolean
+st_button_get_checked (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), FALSE);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->is_checked;
+}
+
+/**
+ * st_button_set_checked:
+ * @button: a #Stbutton
+ * @checked: %TRUE or %FALSE
+ *
+ * Set the #StButton:checked property of the button. This is only really useful
+ * if the button has #StButton:toggle-mode property set to %TRUE.
+ */
+void
+st_button_set_checked (StButton *button,
+ gboolean checked)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+ if (priv->is_checked == checked)
+ return;
+
+ priv->is_checked = checked;
+
+ if (checked)
+ st_widget_add_style_pseudo_class (ST_WIDGET (button), "checked");
+ else
+ st_widget_remove_style_pseudo_class (ST_WIDGET (button), "checked");
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_CHECKED]);
+}
+
+/**
+ * st_button_fake_release:
+ * @button: an #StButton
+ *
+ * If this widget is holding a pointer grab, this function will
+ * will ungrab it, and reset the #StButton:pressed state. The effect is
+ * similar to if the user had released the mouse button, but without
+ * emitting the #StButton::clicked signal.
+ *
+ * This function is useful if for example you want to do something
+ * after the user is holding the mouse button for a given period of
+ * time, breaking the grab.
+ */
+void
+st_button_fake_release (StButton *button)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (priv->grab)
+ {
+ clutter_grab_dismiss (priv->grab);
+ g_clear_pointer (&priv->grab, clutter_grab_unref);
+ }
+
+ priv->grabbed = 0;
+
+ if (priv->pressed || priv->press_sequence)
+ st_button_release (button, priv->device,
+ priv->pressed, 0, NULL);
+}
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+#define ST_TYPE_BUTTON_ACCESSIBLE st_button_accessible_get_type ()
+
+#define ST_BUTTON_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ ST_TYPE_BUTTON_ACCESSIBLE, StButtonAccessible))
+
+#define ST_IS_BUTTON_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ ST_TYPE_BUTTON_ACCESSIBLE))
+
+#define ST_BUTTON_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ ST_TYPE_BUTTON_ACCESSIBLE, StButtonAccessibleClass))
+
+#define ST_IS_BUTTON_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ ST_TYPE_BUTTON_ACCESSIBLE))
+
+#define ST_BUTTON_ACCESSIBLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ ST_TYPE_BUTTON_ACCESSIBLE, StButtonAccessibleClass))
+
+typedef struct _StButtonAccessible StButtonAccessible;
+typedef struct _StButtonAccessibleClass StButtonAccessibleClass;
+
+struct _StButtonAccessible
+{
+ StWidgetAccessible parent;
+};
+
+struct _StButtonAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+/* AtkObject */
+static void st_button_accessible_initialize (AtkObject *obj,
+ gpointer data);
+
+G_DEFINE_TYPE (StButtonAccessible, st_button_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
+
+static const gchar *
+st_button_accessible_get_name (AtkObject *obj)
+{
+ StButton *button = NULL;
+ const gchar *name = NULL;
+
+ button = ST_BUTTON (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (button == NULL)
+ return NULL;
+
+ name = ATK_OBJECT_CLASS (st_button_accessible_parent_class)->get_name (obj);
+ if (name != NULL)
+ return name;
+
+ return st_button_get_label (button);
+}
+
+static void
+st_button_accessible_class_init (StButtonAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ atk_class->initialize = st_button_accessible_initialize;
+ atk_class->get_name = st_button_accessible_get_name;
+}
+
+static void
+st_button_accessible_init (StButtonAccessible *self)
+{
+ /* initialization done on AtkObject->initialize */
+}
+
+static void
+st_button_accessible_notify_label_cb (StButton *button,
+ GParamSpec *psec,
+ AtkObject *accessible)
+{
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+}
+
+static void
+st_button_accessible_compute_role (AtkObject *accessible,
+ StButton *button)
+{
+ atk_object_set_role (accessible, st_button_get_toggle_mode (button)
+ ? ATK_ROLE_TOGGLE_BUTTON : ATK_ROLE_PUSH_BUTTON);
+}
+
+static void
+st_button_accessible_notify_toggle_mode_cb (StButton *button,
+ GParamSpec *psec,
+ AtkObject *accessible)
+{
+ st_button_accessible_compute_role (accessible, button);
+}
+
+static void
+st_button_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_button_accessible_parent_class)->initialize (obj, data);
+
+ st_button_accessible_compute_role (obj, ST_BUTTON (data));
+
+ g_signal_connect (data, "notify::label",
+ G_CALLBACK (st_button_accessible_notify_label_cb), obj);
+ g_signal_connect (data, "notify::toggle-mode",
+ G_CALLBACK (st_button_accessible_notify_toggle_mode_cb), obj);
+}
diff --git a/src/st/st-button.h b/src/st/st-button.h
new file mode 100644
index 0000000..3f7dbb0
--- /dev/null
+++ b/src/st/st-button.h
@@ -0,0 +1,85 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-button.h: Plain button actor
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_BUTTON_H__
+#define __ST_BUTTON_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-bin.h>
+
+#define ST_TYPE_BUTTON (st_button_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StButton, st_button, ST, BUTTON, StBin)
+
+struct _StButtonClass
+{
+ StBinClass parent_class;
+
+ /* vfuncs, not signals */
+ void (* transition) (StButton *button);
+
+ /* signals */
+ void (* clicked) (StButton *button, int clicked_button);
+};
+
+StWidget *st_button_new (void);
+StWidget *st_button_new_with_label (const gchar *text);
+const gchar *st_button_get_label (StButton *button);
+void st_button_set_label (StButton *button,
+ const gchar *text);
+const char *st_button_get_icon_name (StButton *button);
+void st_button_set_icon_name (StButton *button,
+ const char *icon_name);
+void st_button_set_toggle_mode (StButton *button,
+ gboolean toggle);
+gboolean st_button_get_toggle_mode (StButton *button);
+void st_button_set_checked (StButton *button,
+ gboolean checked);
+gboolean st_button_get_checked (StButton *button);
+
+void st_button_fake_release (StButton *button);
+
+/**
+ * StButtonMask:
+ * @ST_BUTTON_ONE: button 1 (left)
+ * @ST_BUTTON_TWO: button 2 (middle)
+ * @ST_BUTTON_THREE: button 3 (right)
+ *
+ * A mask representing which mouse buttons an #StButton responds to.
+ */
+typedef enum {
+ ST_BUTTON_ONE = (1 << 0),
+ ST_BUTTON_TWO = (1 << 1),
+ ST_BUTTON_THREE = (1 << 2),
+} StButtonMask;
+
+#define ST_BUTTON_MASK_FROM_BUTTON(button) (1 << ((button) - 1))
+
+void st_button_set_button_mask (StButton *button,
+ StButtonMask mask);
+StButtonMask st_button_get_button_mask (StButton *button);
+
+G_END_DECLS
+
+#endif /* __ST_BUTTON_H__ */
diff --git a/src/st/st-clipboard.c b/src/st/st-clipboard.c
new file mode 100644
index 0000000..4b730c9
--- /dev/null
+++ b/src/st/st-clipboard.c
@@ -0,0 +1,348 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-clipboard.c: clipboard object
+ *
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-clipboard
+ * @short_description: a simple representation of the clipboard
+ *
+ * #StCliboard is a very simple object representation of the clipboard
+ * available to applications. Text is always assumed to be UTF-8 and non-text
+ * items are not handled.
+ */
+
+#include "config.h"
+
+#include "st-clipboard.h"
+
+#include <meta/display.h>
+#include <meta/meta-selection-source-memory.h>
+#include <meta/meta-selection.h>
+
+G_DEFINE_TYPE (StClipboard, st_clipboard, G_TYPE_OBJECT)
+
+typedef struct _TransferData TransferData;
+struct _TransferData
+{
+ StClipboard *clipboard;
+ GCallback callback;
+ gpointer user_data;
+ GOutputStream *stream;
+};
+
+const char *supported_mimetypes[] = {
+ "text/plain;charset=utf-8",
+ "UTF8_STRING",
+ "text/plain",
+ "STRING",
+};
+
+static MetaSelection *meta_selection = NULL;
+
+static void
+st_clipboard_class_init (StClipboardClass *klass)
+{
+}
+
+static void
+st_clipboard_init (StClipboard *self)
+{
+}
+
+/**
+ * st_clipboard_get_default:
+ *
+ * Get the global #StClipboard object that represents the clipboard.
+ *
+ * Returns: (transfer none): a #StClipboard owned by St and must not be
+ * unrefferenced or freed.
+ */
+StClipboard*
+st_clipboard_get_default (void)
+{
+ static StClipboard *default_clipboard = NULL;
+
+ if (!default_clipboard)
+ {
+ default_clipboard = g_object_new (ST_TYPE_CLIPBOARD, NULL);
+ }
+
+ return default_clipboard;
+}
+
+static gboolean
+convert_type (StClipboardType type,
+ MetaSelectionType *type_out)
+{
+ if (type == ST_CLIPBOARD_TYPE_PRIMARY)
+ *type_out = META_SELECTION_PRIMARY;
+ else if (type == ST_CLIPBOARD_TYPE_CLIPBOARD)
+ *type_out = META_SELECTION_CLIPBOARD;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static const char *
+pick_mimetype (MetaSelection *meta_selection,
+ MetaSelectionType selection_type)
+{
+ const char *selected_mimetype = NULL;
+ GList *mimetypes;
+ int i;
+
+ mimetypes = meta_selection_get_mimetypes (meta_selection, selection_type);
+
+ for (i = 0; i < G_N_ELEMENTS (supported_mimetypes); i++)
+ {
+ if (g_list_find_custom (mimetypes, supported_mimetypes[i],
+ (GCompareFunc) g_strcmp0))
+ {
+ selected_mimetype = supported_mimetypes[i];
+ break;
+ }
+ }
+
+ g_list_free_full (mimetypes, g_free);
+ return selected_mimetype;
+}
+
+static void
+transfer_cb (MetaSelection *selection,
+ GAsyncResult *res,
+ TransferData *data)
+{
+ gchar *text = NULL;
+
+ if (meta_selection_transfer_finish (selection, res, NULL))
+ {
+ gsize data_size;
+
+ data_size =
+ g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (data->stream));
+ text = g_new0 (char, data_size + 1);
+ memcpy (text, g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (data->stream)), data_size);
+ }
+
+ ((StClipboardCallbackFunc) data->callback) (data->clipboard, text,
+ data->user_data);
+ g_object_unref (data->stream);
+ g_free (data);
+ g_free (text);
+}
+
+static void
+transfer_bytes_cb (MetaSelection *selection,
+ GAsyncResult *res,
+ TransferData *data)
+{
+ GBytes *bytes = NULL;
+
+ if (meta_selection_transfer_finish (selection, res, NULL))
+ bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (data->stream));
+
+ ((StClipboardContentCallbackFunc) data->callback) (data->clipboard, bytes,
+ data->user_data);
+ g_object_unref (data->stream);
+ g_clear_pointer (&bytes, g_bytes_unref);
+}
+
+/**
+ * st_clipboard_get_mimetypes:
+ * @clipboard: a #StClipboard
+ *
+ * Gets a list of the mimetypes supported by the default #StClipboard.
+ *
+ * Returns: (element-type utf8) (transfer full): the supported mimetypes
+ */
+GList *
+st_clipboard_get_mimetypes (StClipboard *clipboard,
+ StClipboardType type)
+{
+ MetaSelectionType selection_type;
+
+ g_return_val_if_fail (ST_IS_CLIPBOARD (clipboard), NULL);
+ g_return_val_if_fail (meta_selection != NULL, NULL);
+
+ if (!convert_type (type, &selection_type))
+ return NULL;
+
+ return meta_selection_get_mimetypes (meta_selection, selection_type);
+}
+
+/**
+ * st_clipboard_get_text:
+ * @clipboard: A #StCliboard
+ * @type: The type of clipboard data you want
+ * @callback: (scope async): function to be called when the text is retrieved
+ * @user_data: data to be passed to the callback
+ *
+ * Request the data from the clipboard in text form. @callback is executed
+ * when the data is retrieved.
+ */
+void
+st_clipboard_get_text (StClipboard *clipboard,
+ StClipboardType type,
+ StClipboardCallbackFunc callback,
+ gpointer user_data)
+{
+ MetaSelectionType selection_type;
+ TransferData *data;
+ const char *mimetype = NULL;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (convert_type (type, &selection_type))
+ mimetype = pick_mimetype (meta_selection, selection_type);
+
+ if (!mimetype)
+ {
+ callback (clipboard, NULL, user_data);
+ return;
+ }
+
+ data = g_new0 (TransferData, 1);
+ data->clipboard = clipboard;
+ data->callback = G_CALLBACK (callback);
+ data->user_data = user_data;
+ data->stream = g_memory_output_stream_new_resizable ();
+
+ meta_selection_transfer_async (meta_selection,
+ selection_type,
+ mimetype, -1,
+ data->stream, NULL,
+ (GAsyncReadyCallback) transfer_cb,
+ data);
+}
+
+/**
+ * st_clipboard_get_content:
+ * @clipboard: A #StCliboard
+ * @type: The type of clipboard data you want
+ * @mimetype: The mimetype to get content for
+ * @callback: (scope async): function to be called when the type is retrieved
+ * @user_data: data to be passed to the callback
+ *
+ * Request the data from the clipboard in #GBytes form. @callback is executed
+ * when the data is retrieved.
+ */
+void
+st_clipboard_get_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ StClipboardContentCallbackFunc callback,
+ gpointer user_data)
+{
+ MetaSelectionType selection_type;
+ TransferData *data;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (!mimetype || !convert_type (type, &selection_type))
+ {
+ callback (clipboard, NULL, user_data);
+ return;
+ }
+
+ data = g_new0 (TransferData, 1);
+ data->clipboard = clipboard;
+ data->callback = G_CALLBACK (callback);
+ data->user_data = user_data;
+ data->stream = g_memory_output_stream_new_resizable ();
+
+ meta_selection_transfer_async (meta_selection,
+ selection_type,
+ mimetype, -1,
+ data->stream, NULL,
+ (GAsyncReadyCallback) transfer_bytes_cb,
+ data);
+}
+
+/**
+ * st_clipboard_set_content:
+ * @clipboard: A #StClipboard
+ * @type: The type of clipboard that you want to set
+ * @mimetype: content mimetype
+ * @bytes: content data
+ *
+ * Sets the clipboard content to @bytes.
+ *
+ * @mimetype is a semi-colon separated list of mime-type strings.
+ **/
+void
+st_clipboard_set_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ GBytes *bytes)
+{
+ MetaSelectionType selection_type;
+ MetaSelectionSource *source;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (bytes != NULL);
+
+ if (!convert_type (type, &selection_type))
+ return;
+
+ source = meta_selection_source_memory_new (mimetype, bytes);
+ meta_selection_set_owner (meta_selection, selection_type, source);
+ g_object_unref (source);
+}
+
+/**
+ * st_clipboard_set_text:
+ * @clipboard: A #StClipboard
+ * @type: The type of clipboard that you want to set
+ * @text: text to copy to the clipboard
+ *
+ * Sets text as the current contents of the clipboard.
+ */
+void
+st_clipboard_set_text (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *text)
+{
+ GBytes *bytes;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (text != NULL);
+
+ bytes = g_bytes_new_take (g_strdup (text), strlen (text));
+ st_clipboard_set_content (clipboard, type, "text/plain;charset=utf-8", bytes);
+ g_bytes_unref (bytes);
+}
+
+/**
+ * st_clipboard_set_selection: (skip)
+ *
+ * Sets the #MetaSelection of the default #StClipboard.
+ *
+ * This function is called during the initialization of GNOME Shell.
+ */
+void
+st_clipboard_set_selection (MetaSelection *selection)
+{
+ meta_selection = selection;
+}
diff --git a/src/st/st-clipboard.h b/src/st/st-clipboard.h
new file mode 100644
index 0000000..022b832
--- /dev/null
+++ b/src/st/st-clipboard.h
@@ -0,0 +1,105 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-clipboard.h: clipboard object
+ *
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef _ST_CLIPBOARD_H
+#define _ST_CLIPBOARD_H
+
+#include <glib-object.h>
+#include <meta/meta-selection.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_CLIPBOARD st_clipboard_get_type()
+G_DECLARE_FINAL_TYPE (StClipboard, st_clipboard, ST, CLIPBOARD, GObject)
+
+typedef struct _StClipboard StClipboard;
+
+/**
+ * StClipboard:
+ *
+ * The contents of this structure is private and should only be accessed using
+ * the provided API.
+ */
+struct _StClipboard
+{
+ /*< private >*/
+ GObject parent;
+};
+
+typedef enum {
+ ST_CLIPBOARD_TYPE_PRIMARY,
+ ST_CLIPBOARD_TYPE_CLIPBOARD
+} StClipboardType;
+
+/**
+ * StClipboardCallbackFunc:
+ * @clipboard: A #StClipboard
+ * @text: text from the clipboard
+ * @user_data: user data
+ *
+ * Callback function called when text is retrieved from the clipboard.
+ */
+typedef void (*StClipboardCallbackFunc) (StClipboard *clipboard,
+ const gchar *text,
+ gpointer user_data);
+
+/**
+ * StClipboardContentCallbackFunc:
+ * @clipboard: A #StClipboard
+ * @bytes: content from the clipboard
+ * @user_data: user data
+ *
+ * Callback function called when content is retrieved from the clipboard.
+ */
+typedef void (*StClipboardContentCallbackFunc) (StClipboard *clipboard,
+ GBytes *bytes,
+ gpointer user_data);
+
+StClipboard* st_clipboard_get_default (void);
+
+GList * st_clipboard_get_mimetypes (StClipboard *clipboard,
+ StClipboardType type);
+
+void st_clipboard_get_text (StClipboard *clipboard,
+ StClipboardType type,
+ StClipboardCallbackFunc callback,
+ gpointer user_data);
+void st_clipboard_set_text (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *text);
+
+void st_clipboard_set_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ GBytes *bytes);
+void st_clipboard_get_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ StClipboardContentCallbackFunc callback,
+ gpointer user_data);
+
+void st_clipboard_set_selection (MetaSelection *selection);
+
+G_END_DECLS
+
+#endif /* _ST_CLIPBOARD_H */
diff --git a/src/st/st-drawing-area.c b/src/st/st-drawing-area.c
new file mode 100644
index 0000000..38737fd
--- /dev/null
+++ b/src/st/st-drawing-area.c
@@ -0,0 +1,242 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-drawing-area.c: A dynamically-sized Cairo drawing area
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-drawing-area
+ * @short_description: A dynamically-sized Cairo drawing area
+ *
+ * #StDrawingArea allows drawing via Cairo; the primary difference is that
+ * it is dynamically sized. To use, connect to the #StDrawingArea::repaint
+ * signal, and inside the signal handler, call
+ * st_drawing_area_get_context() to get the Cairo context to draw to. The
+ * #StDrawingArea::repaint signal will be emitted by default when the area is
+ * resized or the CSS style changes; you can use the
+ * st_drawing_area_queue_repaint() as well.
+ */
+
+#include "st-drawing-area.h"
+
+#include <cairo.h>
+#include <math.h>
+
+typedef struct _StDrawingAreaPrivate StDrawingAreaPrivate;
+struct _StDrawingAreaPrivate {
+ cairo_t *context;
+ guint in_repaint : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StDrawingArea, st_drawing_area, ST_TYPE_WIDGET);
+
+/* Signals */
+enum
+{
+ REPAINT,
+ LAST_SIGNAL
+};
+
+static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 };
+
+static gboolean
+draw_content (ClutterCanvas *canvas,
+ cairo_t *cr,
+ int width,
+ int height,
+ gpointer user_data)
+{
+ StDrawingArea *area = ST_DRAWING_AREA (user_data);
+ StDrawingAreaPrivate *priv = st_drawing_area_get_instance_private (area);
+
+ priv->context = cr;
+ priv->in_repaint = TRUE;
+
+ clutter_cairo_clear (cr);
+ g_signal_emit (area, st_drawing_area_signals[REPAINT], 0);
+
+ priv->context = NULL;
+ priv->in_repaint = FALSE;
+
+ return TRUE;
+}
+
+static void
+st_drawing_area_allocate (ClutterActor *self,
+ const ClutterActorBox *box)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ ClutterContent *content = clutter_actor_get_content (self);
+ ClutterActorBox content_box;
+ int width, height;
+ float resource_scale;
+
+ resource_scale = clutter_actor_get_resource_scale (self);
+
+ clutter_actor_set_allocation (self, box);
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ width = (int)(0.5 + content_box.x2 - content_box.x1);
+ height = (int)(0.5 + content_box.y2 - content_box.y1);
+
+ clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
+ clutter_canvas_set_size (CLUTTER_CANVAS (content), width, height);
+}
+
+static void
+st_drawing_area_style_changed (StWidget *self)
+{
+ (ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self);
+
+ st_drawing_area_queue_repaint (ST_DRAWING_AREA (self));
+}
+
+static void
+st_drawing_area_resource_scale_changed (ClutterActor *self)
+{
+ float resource_scale;
+ ClutterContent *content = clutter_actor_get_content (self);
+
+ resource_scale = clutter_actor_get_resource_scale (self);
+ clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
+
+ if (CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class)->resource_scale_changed (self);
+}
+
+static void
+st_drawing_area_class_init (StDrawingAreaClass *klass)
+{
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ actor_class->allocate = st_drawing_area_allocate;
+ widget_class->style_changed = st_drawing_area_style_changed;
+ actor_class->resource_scale_changed = st_drawing_area_resource_scale_changed;
+
+ st_drawing_area_signals[REPAINT] =
+ g_signal_new ("repaint",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StDrawingAreaClass, repaint),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_drawing_area_init (StDrawingArea *area)
+{
+ ClutterContent *content = clutter_canvas_new ();
+ g_signal_connect (content, "draw", G_CALLBACK (draw_content), area);
+ clutter_actor_set_content (CLUTTER_ACTOR (area), content);
+ g_object_unref (content);
+}
+
+/**
+ * st_drawing_area_queue_repaint:
+ * @area: the #StDrawingArea
+ *
+ * Will cause the actor to emit a #StDrawingArea::repaint signal before it is
+ * next drawn to the scene. Useful if some parameters for the area being
+ * drawn other than the size or style have changed. Note that
+ * clutter_actor_queue_redraw() will simply result in the same
+ * contents being drawn to the scene again.
+ */
+void
+st_drawing_area_queue_repaint (StDrawingArea *area)
+{
+ g_return_if_fail (ST_IS_DRAWING_AREA (area));
+
+ clutter_content_invalidate (clutter_actor_get_content (CLUTTER_ACTOR (area)));
+}
+
+/**
+ * st_drawing_area_get_context:
+ * @area: the #StDrawingArea
+ *
+ * Gets the Cairo context to paint to. This function must only be called
+ * from a signal handler or virtual function for the #StDrawingArea::repaint
+ * signal.
+ *
+ * JavaScript code must call the special dispose function before returning from
+ * the signal handler or virtual function to avoid leaking memory:
+ *
+ * |[<!-- language="JavaScript" -->
+ * function onRepaint(area) {
+ * let cr = area.get_context();
+ *
+ * // Draw to the context
+ *
+ * cr.$dispose();
+ * }
+ *
+ * let area = new St.DrawingArea();
+ * area.connect('repaint', onRepaint);
+ * ]|
+ *
+ * Returns: (transfer none): the Cairo context for the paint operation
+ */
+cairo_t *
+st_drawing_area_get_context (StDrawingArea *area)
+{
+ StDrawingAreaPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_DRAWING_AREA (area), NULL);
+
+ priv = st_drawing_area_get_instance_private (area);
+ g_return_val_if_fail (priv->in_repaint, NULL);
+
+ return priv->context;
+}
+
+/**
+ * st_drawing_area_get_surface_size:
+ * @area: the #StDrawingArea
+ * @width: (out) (optional): location to store the width of the painted area
+ * @height: (out) (optional): location to store the height of the painted area
+ *
+ * Gets the size of the cairo surface being painted to, which is equal
+ * to the size of the content area of the widget. This function must
+ * only be called from a signal handler for the #StDrawingArea::repaint signal.
+ */
+void
+st_drawing_area_get_surface_size (StDrawingArea *area,
+ guint *width,
+ guint *height)
+{
+ StDrawingAreaPrivate *priv;
+ ClutterContent *content;
+ float w, h, resource_scale;
+
+ g_return_if_fail (ST_IS_DRAWING_AREA (area));
+
+ priv = st_drawing_area_get_instance_private (area);
+ g_return_if_fail (priv->in_repaint);
+
+ content = clutter_actor_get_content (CLUTTER_ACTOR (area));
+ clutter_content_get_preferred_size (content, &w, &h);
+
+ resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (area));
+
+ w /= resource_scale;
+ h /= resource_scale;
+
+ if (width)
+ *width = ceilf (w);
+ if (height)
+ *height = ceilf (h);
+}
diff --git a/src/st/st-drawing-area.h b/src/st/st-drawing-area.h
new file mode 100644
index 0000000..e09f9c5
--- /dev/null
+++ b/src/st/st-drawing-area.h
@@ -0,0 +1,44 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-drawing-area.h: A dynamically-sized Cairo drawing area
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_DRAWING_AREA_H__
+#define __ST_DRAWING_AREA_H__
+
+#include "st-widget.h"
+#include <cairo.h>
+
+#define ST_TYPE_DRAWING_AREA (st_drawing_area_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StDrawingArea, st_drawing_area,
+ ST, DRAWING_AREA, StWidget)
+
+struct _StDrawingAreaClass
+{
+ StWidgetClass parent_class;
+
+ void (*repaint) (StDrawingArea *area);
+};
+
+void st_drawing_area_queue_repaint (StDrawingArea *area);
+cairo_t *st_drawing_area_get_context (StDrawingArea *area);
+void st_drawing_area_get_surface_size (StDrawingArea *area,
+ guint *width,
+ guint *height);
+
+#endif /* __ST_DRAWING_AREA_H__ */
diff --git a/src/st/st-entry.c b/src/st/st-entry.c
new file mode 100644
index 0000000..64f85fd
--- /dev/null
+++ b/src/st/st-entry.c
@@ -0,0 +1,1626 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-entry.c: Plain entry actor
+ *
+ * Copyright 2008, 2009 Intel Corporation
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-entry
+ * @short_description: Widget for displaying text
+ *
+ * #StEntry is a simple widget for displaying text. It derives from
+ * #StWidget to add extra style and placement functionality over
+ * #ClutterText. The internal #ClutterText is publicly accessibly to allow
+ * applications to set further properties.
+ *
+ * #StEntry supports the following pseudo style states:
+ *
+ * - `focus`: the widget has focus
+ * - `indeterminate`: the widget is showing the hint text or actor
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+#include "st-entry.h"
+
+#include "st-icon.h"
+#include "st-label.h"
+#include "st-settings.h"
+#include "st-widget.h"
+#include "st-texture-cache.h"
+#include "st-clipboard.h"
+#include "st-private.h"
+
+#include "st-widget-accessible.h"
+
+
+/* properties */
+enum
+{
+ PROP_0,
+
+ PROP_CLUTTER_TEXT,
+ PROP_PRIMARY_ICON,
+ PROP_SECONDARY_ICON,
+ PROP_HINT_TEXT,
+ PROP_HINT_ACTOR,
+ PROP_TEXT,
+ PROP_INPUT_PURPOSE,
+ PROP_INPUT_HINTS,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+/* signals */
+enum
+{
+ PRIMARY_ICON_CLICKED,
+ SECONDARY_ICON_CLICKED,
+
+ LAST_SIGNAL
+};
+
+#define ST_ENTRY_PRIV(x) st_entry_get_instance_private ((StEntry *) x)
+
+
+typedef struct _StEntryPrivate StEntryPrivate;
+struct _StEntryPrivate
+{
+ ClutterActor *entry;
+
+ ClutterActor *primary_icon;
+ ClutterActor *secondary_icon;
+
+ ClutterActor *hint_actor;
+
+ gfloat spacing;
+
+ gboolean has_ibeam;
+
+ StShadow *shadow_spec;
+
+ CoglPipeline *text_shadow_material;
+ gfloat shadow_width;
+ gfloat shadow_height;
+};
+
+static guint entry_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StEntry, st_entry, ST_TYPE_WIDGET);
+
+static GType st_entry_accessible_get_type (void) G_GNUC_CONST;
+
+static void
+st_entry_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StEntry *entry = ST_ENTRY (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PRIMARY_ICON:
+ st_entry_set_primary_icon (entry, g_value_get_object (value));
+ break;
+
+ case PROP_SECONDARY_ICON:
+ st_entry_set_secondary_icon (entry, g_value_get_object (value));
+ break;
+
+ case PROP_HINT_TEXT:
+ st_entry_set_hint_text (entry, g_value_get_string (value));
+ break;
+
+ case PROP_HINT_ACTOR:
+ st_entry_set_hint_actor (entry, g_value_get_object (value));
+ break;
+
+ case PROP_TEXT:
+ st_entry_set_text (entry, g_value_get_string (value));
+ break;
+
+ case PROP_INPUT_PURPOSE:
+ st_entry_set_input_purpose (entry, g_value_get_enum (value));
+ break;
+
+ case PROP_INPUT_HINTS:
+ st_entry_set_input_hints (entry, g_value_get_flags (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_entry_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CLUTTER_TEXT:
+ g_value_set_object (value, priv->entry);
+ break;
+
+ case PROP_PRIMARY_ICON:
+ g_value_set_object (value, priv->primary_icon);
+ break;
+
+ case PROP_SECONDARY_ICON:
+ g_value_set_object (value, priv->secondary_icon);
+ break;
+
+ case PROP_HINT_TEXT:
+ g_value_set_string (value, st_entry_get_hint_text (ST_ENTRY (gobject)));
+ break;
+
+ case PROP_HINT_ACTOR:
+ g_value_set_object (value, priv->hint_actor);
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->entry)));
+ break;
+
+ case PROP_INPUT_PURPOSE:
+ g_value_set_enum (value, clutter_text_get_input_purpose (CLUTTER_TEXT (priv->entry)));
+ break;
+
+ case PROP_INPUT_HINTS:
+ g_value_set_flags (value, clutter_text_get_input_hints (CLUTTER_TEXT (priv->entry)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_entry_dispose (GObject *object)
+{
+ StEntry *entry = ST_ENTRY (object);
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ cogl_clear_object (&priv->text_shadow_material);
+
+ G_OBJECT_CLASS (st_entry_parent_class)->dispose (object);
+}
+
+static void
+st_entry_update_hint_visibility (StEntry *self)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (self);
+ gboolean hint_visible =
+ priv->hint_actor != NULL &&
+ !clutter_text_has_preedit (CLUTTER_TEXT (priv->entry)) &&
+ strcmp (clutter_text_get_text (CLUTTER_TEXT (priv->entry)), "") == 0;
+
+ if (priv->hint_actor)
+ g_object_set (priv->hint_actor, "visible", hint_visible, NULL);
+
+ if (hint_visible)
+ st_widget_add_style_pseudo_class (ST_WIDGET (self), "indeterminate");
+ else
+ st_widget_remove_style_pseudo_class (ST_WIDGET (self), "indeterminate");
+}
+
+static void
+st_entry_style_changed (StWidget *self)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (self);
+ StThemeNode *theme_node;
+ StShadow *shadow_spec;
+ ClutterColor color;
+ gdouble size;
+
+ theme_node = st_widget_get_theme_node (self);
+
+ shadow_spec = st_theme_node_get_text_shadow (theme_node);
+ if (!priv->shadow_spec || !shadow_spec ||
+ !st_shadow_equal (shadow_spec, priv->shadow_spec))
+ {
+ g_clear_pointer (&priv->text_shadow_material, cogl_object_unref);
+
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+ if (shadow_spec)
+ priv->shadow_spec = st_shadow_ref (shadow_spec);
+ }
+
+ _st_set_text_from_style (CLUTTER_TEXT (priv->entry), theme_node);
+
+ if (st_theme_node_lookup_length (theme_node, "caret-size", TRUE, &size))
+ clutter_text_set_cursor_size (CLUTTER_TEXT (priv->entry), (int)(.5 + size));
+
+ if (st_theme_node_lookup_color (theme_node, "caret-color", TRUE, &color))
+ clutter_text_set_cursor_color (CLUTTER_TEXT (priv->entry), &color);
+
+ if (st_theme_node_lookup_color (theme_node, "selection-background-color", TRUE, &color))
+ clutter_text_set_selection_color (CLUTTER_TEXT (priv->entry), &color);
+
+ if (st_theme_node_lookup_color (theme_node, "selected-color", TRUE, &color))
+ clutter_text_set_selected_text_color (CLUTTER_TEXT (priv->entry), &color);
+
+ ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
+}
+
+static gboolean
+st_entry_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
+
+ /* This is basically the same as st_widget_real_navigate_focus(),
+ * except that widget is behaving as a proxy for priv->entry (which
+ * isn't an StWidget and so has no can-focus flag of its own).
+ */
+
+ if (from == priv->entry)
+ return FALSE;
+ else if (st_widget_get_can_focus (widget) &&
+ clutter_actor_is_mapped (priv->entry))
+ {
+ clutter_actor_grab_key_focus (priv->entry);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static void
+st_entry_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gfloat hint_w, hint_min_w, icon_w;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ clutter_actor_get_preferred_width (priv->entry, for_height,
+ min_width_p,
+ natural_width_p);
+
+ if (priv->hint_actor)
+ {
+ clutter_actor_get_preferred_width (priv->hint_actor, -1,
+ &hint_min_w, &hint_w);
+
+ if (min_width_p && hint_min_w > *min_width_p)
+ *min_width_p = hint_min_w;
+
+ if (natural_width_p && hint_w > *natural_width_p)
+ *natural_width_p = hint_w;
+ }
+
+ if (priv->primary_icon)
+ {
+ clutter_actor_get_preferred_width (priv->primary_icon, -1, NULL, &icon_w);
+
+ if (min_width_p)
+ *min_width_p += icon_w + priv->spacing;
+
+ if (natural_width_p)
+ *natural_width_p += icon_w + priv->spacing;
+ }
+
+ if (priv->secondary_icon)
+ {
+ clutter_actor_get_preferred_width (priv->secondary_icon,
+ -1, NULL, &icon_w);
+
+ if (min_width_p)
+ *min_width_p += icon_w + priv->spacing;
+
+ if (natural_width_p)
+ *natural_width_p += icon_w + priv->spacing;
+ }
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_entry_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gfloat hint_h, icon_h;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ clutter_actor_get_preferred_height (priv->entry, for_width,
+ min_height_p,
+ natural_height_p);
+
+ if (priv->hint_actor)
+ {
+ clutter_actor_get_preferred_height (priv->hint_actor, -1, NULL, &hint_h);
+
+ if (min_height_p && hint_h > *min_height_p)
+ *min_height_p = hint_h;
+
+ if (natural_height_p && hint_h > *natural_height_p)
+ *natural_height_p = hint_h;
+ }
+
+ if (priv->primary_icon)
+ {
+ clutter_actor_get_preferred_height (priv->primary_icon,
+ -1, NULL, &icon_h);
+
+ if (min_height_p && icon_h > *min_height_p)
+ *min_height_p = icon_h;
+
+ if (natural_height_p && icon_h > *natural_height_p)
+ *natural_height_p = icon_h;
+ }
+
+ if (priv->secondary_icon)
+ {
+ clutter_actor_get_preferred_height (priv->secondary_icon,
+ -1, NULL, &icon_h);
+
+ if (min_height_p && icon_h > *min_height_p)
+ *min_height_p = icon_h;
+
+ if (natural_height_p && icon_h > *natural_height_p)
+ *natural_height_p = icon_h;
+ }
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_entry_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box, child_box, icon_box, hint_box;
+ gfloat icon_w, icon_h;
+ gfloat hint_w, hint_min_w, hint_h;
+ gfloat entry_h, min_h, pref_h, avail_h;
+ ClutterActor *left_icon, *right_icon;
+ gboolean is_rtl;
+
+ is_rtl = clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL;
+
+ if (is_rtl)
+ {
+ right_icon = priv->primary_icon;
+ left_icon = priv->secondary_icon;
+ }
+ else
+ {
+ left_icon = priv->primary_icon;
+ right_icon = priv->secondary_icon;
+ }
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ avail_h = content_box.y2 - content_box.y1;
+
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x2;
+
+ if (left_icon)
+ {
+ clutter_actor_get_preferred_width (left_icon, -1, NULL, &icon_w);
+ clutter_actor_get_preferred_height (left_icon, -1, NULL, &icon_h);
+
+ icon_box.x1 = content_box.x1;
+ icon_box.x2 = icon_box.x1 + icon_w;
+
+ icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
+ icon_box.y2 = icon_box.y1 + icon_h;
+
+ clutter_actor_allocate (left_icon, &icon_box);
+
+ /* reduce the size for the entry */
+ child_box.x1 = MIN (child_box.x2, child_box.x1 + icon_w + priv->spacing);
+ }
+
+ if (right_icon)
+ {
+ clutter_actor_get_preferred_width (right_icon, -1, NULL, &icon_w);
+ clutter_actor_get_preferred_height (right_icon, -1, NULL, &icon_h);
+
+ icon_box.x2 = content_box.x2;
+ icon_box.x1 = icon_box.x2 - icon_w;
+
+ icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
+ icon_box.y2 = icon_box.y1 + icon_h;
+
+ clutter_actor_allocate (right_icon, &icon_box);
+
+ /* reduce the size for the entry */
+ child_box.x2 = MAX (child_box.x1, child_box.x2 - icon_w - priv->spacing);
+ }
+
+ if (priv->hint_actor)
+ {
+ /* now allocate the hint actor */
+ hint_box = child_box;
+
+ clutter_actor_get_preferred_width (priv->hint_actor, -1, &hint_min_w, &hint_w);
+ clutter_actor_get_preferred_height (priv->hint_actor, -1, NULL, &hint_h);
+
+ hint_w = CLAMP (hint_w, hint_min_w, child_box.x2 - child_box.x1);
+
+ if (is_rtl)
+ hint_box.x1 = hint_box.x2 - hint_w;
+ else
+ hint_box.x2 = hint_box.x1 + hint_w;
+
+ hint_box.y1 = ceil (content_box.y1 + avail_h / 2 - hint_h / 2);
+ hint_box.y2 = hint_box.y1 + hint_h;
+
+ clutter_actor_allocate (priv->hint_actor, &hint_box);
+ }
+
+ clutter_actor_get_preferred_height (priv->entry, child_box.x2 - child_box.x1,
+ &min_h, &pref_h);
+
+ entry_h = CLAMP (pref_h, min_h, avail_h);
+
+ child_box.y1 = (int) (content_box.y1 + avail_h / 2 - entry_h / 2);
+ child_box.y2 = child_box.y1 + entry_h;
+
+ clutter_actor_allocate (priv->entry, &child_box);
+}
+
+static void
+clutter_text_reactive_changed_cb (ClutterActor *text,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ClutterActor *stage;
+
+ if (clutter_actor_get_reactive (text))
+ return;
+
+ if (!clutter_actor_has_key_focus (text))
+ return;
+
+ stage = clutter_actor_get_stage (text);
+ if (stage == NULL)
+ return;
+
+ clutter_stage_set_key_focus (CLUTTER_STAGE (stage), NULL);
+}
+
+static void
+clutter_text_focus_in_cb (ClutterText *text,
+ ClutterActor *actor)
+{
+ st_widget_add_style_pseudo_class (ST_WIDGET (actor), "focus");
+ clutter_text_set_cursor_visible (text, TRUE);
+}
+
+static void
+clutter_text_focus_out_cb (ClutterText *text,
+ ClutterActor *actor)
+{
+ st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "focus");
+ clutter_text_set_cursor_visible (text, FALSE);
+}
+
+static void
+clutter_text_cursor_changed (ClutterText *text,
+ StEntry *entry)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ st_entry_update_hint_visibility (entry);
+
+ g_clear_pointer (&priv->text_shadow_material, cogl_object_unref);
+}
+
+static void
+clutter_text_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StEntry *entry = ST_ENTRY (user_data);
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ st_entry_update_hint_visibility (entry);
+
+ /* Since the text changed, force a regen of the shadow texture */
+ cogl_clear_object (&priv->text_shadow_material);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_TEXT]);
+}
+
+static void
+invalidate_shadow_pipeline (GObject *object,
+ GParamSpec *pspec,
+ StEntry *entry)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ g_clear_pointer (&priv->text_shadow_material, cogl_object_unref);
+}
+
+static void
+st_entry_clipboard_callback (StClipboard *clipboard,
+ const gchar *text,
+ gpointer data)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (data);
+ ClutterText *ctext = (ClutterText*)priv->entry;
+ gint cursor_pos;
+
+ if (!text)
+ return;
+
+ /* delete the current selection before pasting */
+ clutter_text_delete_selection (ctext);
+
+ /* "paste" the clipboard text into the entry */
+ cursor_pos = clutter_text_get_cursor_position (ctext);
+ clutter_text_insert_text (ctext, text, cursor_pos);
+}
+
+static gboolean
+clutter_text_button_press_event (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ gpointer user_data)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (user_data);
+
+ if (event->button == 2 &&
+ clutter_text_get_editable (CLUTTER_TEXT (priv->entry)))
+ {
+ StSettings *settings;
+ gboolean primary_paste_enabled;
+
+ settings = st_settings_get ();
+ g_object_get (settings, "primary-paste", &primary_paste_enabled, NULL);
+
+ if (primary_paste_enabled)
+ {
+ StClipboard *clipboard;
+
+ clipboard = st_clipboard_get_default ();
+
+ /* By the time the clipboard callback is called,
+ * the rest of the signal handlers will have
+ * run, making the text cursor to be in the correct
+ * place.
+ */
+ st_clipboard_get_text (clipboard,
+ ST_CLIPBOARD_TYPE_PRIMARY,
+ st_entry_clipboard_callback,
+ user_data);
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_entry_key_press_event (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+
+ /* This is expected to handle events that were emitted for the inner
+ ClutterText. They only reach this function if the ClutterText
+ didn't handle them */
+
+ /* paste */
+ if (((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && event->keyval == CLUTTER_KEY_v) ||
+ ((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && event->keyval == CLUTTER_KEY_V) ||
+ ((event->modifier_state & CLUTTER_SHIFT_MASK)
+ && event->keyval == CLUTTER_KEY_Insert))
+ {
+ StClipboard *clipboard;
+
+ clipboard = st_clipboard_get_default ();
+
+ st_clipboard_get_text (clipboard,
+ ST_CLIPBOARD_TYPE_CLIPBOARD,
+ st_entry_clipboard_callback,
+ actor);
+
+ return TRUE;
+ }
+
+ /* copy */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && (event->keyval == CLUTTER_KEY_c || event->keyval == CLUTTER_KEY_C) &&
+ clutter_text_get_password_char ((ClutterText*) priv->entry) == 0)
+ {
+ StClipboard *clipboard;
+ gchar *text;
+
+ clipboard = st_clipboard_get_default ();
+
+ text = clutter_text_get_selection ((ClutterText*) priv->entry);
+
+ if (text && strlen (text))
+ st_clipboard_set_text (clipboard,
+ ST_CLIPBOARD_TYPE_CLIPBOARD,
+ text);
+
+ g_free (text);
+
+ return TRUE;
+ }
+
+
+ /* cut */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && (event->keyval == CLUTTER_KEY_x || event->keyval == CLUTTER_KEY_X) &&
+ clutter_text_get_password_char ((ClutterText*) priv->entry) == 0)
+ {
+ StClipboard *clipboard;
+ gchar *text;
+
+ clipboard = st_clipboard_get_default ();
+
+ text = clutter_text_get_selection ((ClutterText*) priv->entry);
+
+ if (text && strlen (text))
+ {
+ st_clipboard_set_text (clipboard,
+ ST_CLIPBOARD_TYPE_CLIPBOARD,
+ text);
+
+ /* now delete the text */
+ clutter_text_delete_selection ((ClutterText *) priv->entry);
+ }
+
+ g_free (text);
+
+ return TRUE;
+ }
+
+
+ /* delete to beginning of line */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK) &&
+ (event->keyval == CLUTTER_KEY_u || event->keyval == CLUTTER_KEY_U))
+ {
+ int pos = clutter_text_get_cursor_position ((ClutterText *)priv->entry);
+ clutter_text_delete_text ((ClutterText *)priv->entry, 0, pos);
+
+ return TRUE;
+ }
+
+
+ /* delete to end of line */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK) &&
+ (event->keyval == CLUTTER_KEY_k || event->keyval == CLUTTER_KEY_K))
+ {
+ ClutterTextBuffer *buffer = clutter_text_get_buffer ((ClutterText *)priv->entry);
+ int pos = clutter_text_get_cursor_position ((ClutterText *)priv->entry);
+ clutter_text_buffer_delete_text (buffer, pos, -1);
+
+ return TRUE;
+ }
+
+ return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->key_press_event (actor, event);
+}
+
+static void
+st_entry_key_focus_in (ClutterActor *actor)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+
+ /* We never want key focus. The ClutterText should be given first
+ pass for all key events */
+ clutter_actor_grab_key_focus (priv->entry);
+}
+
+static StEntryCursorFunc cursor_func = NULL;
+static gpointer cursor_func_data = NULL;
+
+/**
+ * st_entry_set_cursor_func: (skip)
+ *
+ * This function is for private use by libgnome-shell.
+ * Do not ever use.
+ */
+void
+st_entry_set_cursor_func (StEntryCursorFunc func,
+ gpointer data)
+{
+ cursor_func = func;
+ cursor_func_data = data;
+}
+
+static void
+st_entry_set_cursor (StEntry *entry,
+ gboolean use_ibeam)
+{
+ if (cursor_func)
+ cursor_func (entry, use_ibeam, cursor_func_data);
+
+ ((StEntryPrivate *)ST_ENTRY_PRIV (entry))->has_ibeam = use_ibeam;
+}
+
+static gboolean
+st_entry_enter_event (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ ClutterStage *stage;
+ ClutterActor *target;
+
+ stage = clutter_event_get_stage ((ClutterEvent *) event);
+ target = clutter_stage_get_event_actor (stage, (ClutterEvent *) event);
+
+ if (target == priv->entry && event->related != NULL)
+ st_entry_set_cursor (ST_ENTRY (actor), TRUE);
+
+ return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->enter_event (actor, event);
+}
+
+static gboolean
+st_entry_leave_event (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ st_entry_set_cursor (ST_ENTRY (actor), FALSE);
+
+ return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->leave_event (actor, event);
+}
+
+static void
+st_entry_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ ClutterActorClass *parent_class;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->shadow_spec)
+ {
+ ClutterActorBox allocation;
+ float width, height;
+
+ clutter_actor_get_allocation_box (priv->entry, &allocation);
+ clutter_actor_box_get_size (&allocation, &width, &height);
+
+ if (priv->text_shadow_material == NULL ||
+ width != priv->shadow_width ||
+ height != priv->shadow_height)
+ {
+ CoglPipeline *material;
+
+ cogl_clear_object (&priv->text_shadow_material);
+
+ material = _st_create_shadow_pipeline_from_actor (priv->shadow_spec,
+ priv->entry);
+
+ priv->shadow_width = width;
+ priv->shadow_height = height;
+ priv->text_shadow_material = material;
+ }
+
+ if (priv->text_shadow_material != NULL)
+ {
+ CoglFramebuffer *framebuffer =
+ clutter_paint_context_get_framebuffer (paint_context);
+
+ _st_paint_shadow_with_opacity (priv->shadow_spec,
+ framebuffer,
+ priv->text_shadow_material,
+ &allocation,
+ clutter_actor_get_paint_opacity (priv->entry));
+ }
+ }
+
+ /* Since we paint the background ourselves, chain to the parent class
+ * of StWidget, to avoid painting it twice.
+ * This is needed as we still want to paint children.
+ */
+ parent_class = g_type_class_peek_parent (st_entry_parent_class);
+ parent_class->paint (actor, paint_context);
+}
+
+static void
+st_entry_unmap (ClutterActor *actor)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ if (priv->has_ibeam)
+ st_entry_set_cursor (ST_ENTRY (actor), FALSE);
+
+ CLUTTER_ACTOR_CLASS (st_entry_parent_class)->unmap (actor);
+}
+
+static gboolean
+st_entry_get_paint_volume (ClutterActor *actor,
+ ClutterPaintVolume *volume)
+{
+ return clutter_paint_volume_set_from_allocation (volume, actor);
+}
+
+static void
+st_entry_class_init (StEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_entry_set_property;
+ gobject_class->get_property = st_entry_get_property;
+ gobject_class->dispose = st_entry_dispose;
+
+ actor_class->get_preferred_width = st_entry_get_preferred_width;
+ actor_class->get_preferred_height = st_entry_get_preferred_height;
+ actor_class->allocate = st_entry_allocate;
+ actor_class->paint = st_entry_paint;
+ actor_class->unmap = st_entry_unmap;
+ actor_class->get_paint_volume = st_entry_get_paint_volume;
+
+ actor_class->key_press_event = st_entry_key_press_event;
+ actor_class->key_focus_in = st_entry_key_focus_in;
+
+ actor_class->enter_event = st_entry_enter_event;
+ actor_class->leave_event = st_entry_leave_event;
+
+ widget_class->style_changed = st_entry_style_changed;
+ widget_class->navigate_focus = st_entry_navigate_focus;
+ widget_class->get_accessible_type = st_entry_accessible_get_type;
+
+ /**
+ * StEntry:clutter-text:
+ *
+ * The internal #ClutterText actor supporting the #StEntry.
+ */
+ props[PROP_CLUTTER_TEXT] =
+ g_param_spec_object ("clutter-text",
+ "Clutter Text",
+ "Internal ClutterText actor",
+ CLUTTER_TYPE_TEXT,
+ ST_PARAM_READABLE);
+
+ /**
+ * StEntry:primary-icon:
+ *
+ * The #ClutterActor acting as the primary icon at the start of the #StEntry.
+ */
+ props[PROP_PRIMARY_ICON] =
+ g_param_spec_object ("primary-icon",
+ "Primary Icon",
+ "Primary Icon actor",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:secondary-icon:
+ *
+ * The #ClutterActor acting as the secondary icon at the end of the #StEntry.
+ */
+ props[PROP_SECONDARY_ICON] =
+ g_param_spec_object ("secondary-icon",
+ "Secondary Icon",
+ "Secondary Icon actor",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:hint-text:
+ *
+ * The text to display when the entry is empty and unfocused. Setting this
+ * will replace the actor of #StEntry::hint-actor.
+ */
+ props[PROP_HINT_TEXT] =
+ g_param_spec_string ("hint-text",
+ "Hint Text",
+ "Text to display when the entry is not focused "
+ "and the text property is empty",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:hint-actor:
+ *
+ * A #ClutterActor to display when the entry is empty and unfocused. Setting
+ * this will replace the actor displaying #StEntry:hint-text.
+ */
+ props[PROP_HINT_ACTOR] =
+ g_param_spec_object ("hint-actor",
+ "Hint Actor",
+ "An actor to display when the entry is not focused "
+ "and the text property is empty",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:text:
+ *
+ * The current text value of the #StEntry.
+ */
+ props[PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "Text of the entry",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:input-purpose:
+ *
+ * The #ClutterInputContentPurpose that helps on-screen keyboards and similar
+ * input methods to decide which keys should be presented to the user.
+ */
+ props[PROP_INPUT_PURPOSE] =
+ g_param_spec_enum ("input-purpose",
+ "Purpose",
+ "Purpose of the text field",
+ CLUTTER_TYPE_INPUT_CONTENT_PURPOSE,
+ CLUTTER_INPUT_CONTENT_PURPOSE_NORMAL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:input-hints:
+ *
+ * The #ClutterInputContentHintFlags providing additional hints (beyond
+ * #StEntry:input-purpose) that allow input methods to fine-tune their
+ * behaviour.
+ */
+ props[PROP_INPUT_HINTS] =
+ g_param_spec_flags ("input-hints",
+ "hints",
+ "Hints for the text field behaviour",
+ CLUTTER_TYPE_INPUT_CONTENT_HINT_FLAGS,
+ 0,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+ /* signals */
+ /**
+ * StEntry::primary-icon-clicked:
+ * @self: the #StEntry
+ *
+ * Emitted when the primary icon is clicked.
+ */
+ entry_signals[PRIMARY_ICON_CLICKED] =
+ g_signal_new ("primary-icon-clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StEntryClass, primary_icon_clicked),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StEntry::secondary-icon-clicked:
+ * @self: the #StEntry
+ *
+ * Emitted when the secondary icon is clicked.
+ */
+ entry_signals[SECONDARY_ICON_CLICKED] =
+ g_signal_new ("secondary-icon-clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StEntryClass, secondary_icon_clicked),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_entry_init (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ priv = st_entry_get_instance_private (entry);
+
+ priv->entry = g_object_new (CLUTTER_TYPE_TEXT,
+ "line-alignment", PANGO_ALIGN_LEFT,
+ "editable", TRUE,
+ "reactive", TRUE,
+ "single-line-mode", TRUE,
+ NULL);
+
+ g_object_bind_property (G_OBJECT (entry), "reactive",
+ priv->entry, "reactive",
+ G_BINDING_DEFAULT);
+
+ g_signal_connect(priv->entry, "notify::reactive",
+ G_CALLBACK (clutter_text_reactive_changed_cb), entry);
+
+ g_signal_connect (priv->entry, "key-focus-in",
+ G_CALLBACK (clutter_text_focus_in_cb), entry);
+
+ g_signal_connect (priv->entry, "key-focus-out",
+ G_CALLBACK (clutter_text_focus_out_cb), entry);
+
+ g_signal_connect (priv->entry, "button-press-event",
+ G_CALLBACK (clutter_text_button_press_event), entry);
+
+ g_signal_connect (priv->entry, "cursor-changed",
+ G_CALLBACK (clutter_text_cursor_changed), entry);
+
+ g_signal_connect (priv->entry, "notify::text",
+ G_CALLBACK (clutter_text_changed_cb), entry);
+
+ /* These properties might get set from CSS using _st_set_text_from_style */
+ g_signal_connect (priv->entry, "notify::font-description",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+ g_signal_connect (priv->entry, "notify::attributes",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+ g_signal_connect (priv->entry, "notify::justify",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+ g_signal_connect (priv->entry, "notify::line-alignment",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+
+ priv->spacing = 6.0f;
+
+ priv->text_shadow_material = NULL;
+ priv->shadow_width = -1.;
+ priv->shadow_height = -1.;
+
+ clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->entry);
+ clutter_actor_set_reactive ((ClutterActor *) entry, TRUE);
+
+ /* set cursor hidden until we receive focus */
+ clutter_text_set_cursor_visible ((ClutterText *) priv->entry, FALSE);
+}
+
+/**
+ * st_entry_new:
+ * @text: (nullable): text to set the entry to
+ *
+ * Create a new #StEntry with the specified text.
+ *
+ * Returns: a new #StEntry
+ */
+StWidget *
+st_entry_new (const gchar *text)
+{
+ StWidget *entry;
+
+ /* add the entry to the stage, but don't allow it to be visible */
+ entry = g_object_new (ST_TYPE_ENTRY,
+ "text", text,
+ NULL);
+
+ return entry;
+}
+
+/**
+ * st_entry_get_text:
+ * @entry: a #StEntry
+ *
+ * Get the text displayed on the entry. If @entry is empty, an empty string will
+ * be returned instead of %NULL.
+ *
+ * Returns: (transfer none): the text for the entry
+ */
+const gchar *
+st_entry_get_text (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = st_entry_get_instance_private (entry);
+
+ return clutter_text_get_text (CLUTTER_TEXT (priv->entry));
+}
+
+/**
+ * st_entry_set_text:
+ * @entry: a #StEntry
+ * @text: (nullable): text to set the entry to
+ *
+ * Sets the text displayed on the entry. If @text is %NULL, the #ClutterText
+ * will instead be set to an empty string.
+ */
+void
+st_entry_set_text (StEntry *entry,
+ const gchar *text)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+
+ clutter_text_set_text (CLUTTER_TEXT (priv->entry), text);
+
+ /* Note: PROP_TEXT will get notfied from our notify::text handler connected
+ * to priv->entry. */
+}
+
+/**
+ * st_entry_get_clutter_text:
+ * @entry: a #StEntry
+ *
+ * Retrieve the internal #ClutterText so that extra parameters can be set.
+ *
+ * Returns: (transfer none): the #ClutterText used by @entry
+ */
+ClutterActor*
+st_entry_get_clutter_text (StEntry *entry)
+{
+ g_return_val_if_fail (ST_ENTRY (entry), NULL);
+
+ return ((StEntryPrivate *)ST_ENTRY_PRIV (entry))->entry;
+}
+
+/**
+ * st_entry_set_hint_text:
+ * @entry: a #StEntry
+ * @text: (nullable): text to set as the entry hint
+ *
+ * Sets the text to display when the entry is empty and unfocused. When the
+ * entry is displaying the hint, it has a pseudo class of `indeterminate`.
+ * A value of %NULL unsets the hint.
+ */
+void
+st_entry_set_hint_text (StEntry *entry,
+ const gchar *text)
+{
+ StWidget *label;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ label = st_label_new (text);
+ st_widget_add_style_class_name (label, "hint-text");
+
+ st_entry_set_hint_actor (ST_ENTRY (entry), CLUTTER_ACTOR (label));
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HINT_TEXT]);
+}
+
+/**
+ * st_entry_get_hint_text:
+ * @entry: a #StEntry
+ *
+ * Gets the text that is displayed when the entry is empty and unfocused or
+ * %NULL if the #StEntry:hint-actor was set to an actor that is not a #StLabel.
+ *
+ * Unlike st_entry_get_text() this function may return %NULL if
+ * #StEntry:hint-actor is not a #StLabel.
+ *
+ * Returns: (nullable) (transfer none): the current value of the hint property
+ */
+const gchar *
+st_entry_get_hint_text (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+
+ if (priv->hint_actor != NULL && ST_IS_LABEL (priv->hint_actor))
+ return st_label_get_text (ST_LABEL (priv->hint_actor));
+
+ return NULL;
+}
+
+/**
+ * st_entry_set_input_purpose:
+ * @entry: a #StEntry
+ * @purpose: the purpose
+ *
+ * Sets the #StEntry:input-purpose property which
+ * can be used by on-screen keyboards and other input
+ * methods to adjust their behaviour.
+ */
+void
+st_entry_set_input_purpose (StEntry *entry,
+ ClutterInputContentPurpose purpose)
+{
+ StEntryPrivate *priv;
+ ClutterText *editable;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+ editable = CLUTTER_TEXT (priv->entry);
+
+ if (clutter_text_get_input_purpose (editable) != purpose)
+ {
+ clutter_text_set_input_purpose (editable, purpose);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_INPUT_PURPOSE]);
+ }
+}
+
+/**
+ * st_entry_get_input_purpose:
+ * @entry: a #StEntry
+ *
+ * Gets the value of the #StEntry:input-purpose property.
+ *
+ * Returns: the input purpose of the entry
+ */
+ClutterInputContentPurpose
+st_entry_get_input_purpose (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), CLUTTER_INPUT_CONTENT_PURPOSE_NORMAL);
+
+ priv = st_entry_get_instance_private (entry);
+ return clutter_text_get_input_purpose (CLUTTER_TEXT (priv->entry));
+}
+
+/**
+ * st_entry_set_input_hints:
+ * @entry: a #StEntry
+ * @hints: the hints
+ *
+ * Sets the #StEntry:input-hints property, which
+ * allows input methods to fine-tune their behaviour.
+ */
+void
+st_entry_set_input_hints (StEntry *entry,
+ ClutterInputContentHintFlags hints)
+{
+ StEntryPrivate *priv;
+ ClutterText *editable;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+ editable = CLUTTER_TEXT (priv->entry);
+
+ if (clutter_text_get_input_hints (editable) != hints)
+ {
+ clutter_text_set_input_hints (editable, hints);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_INPUT_HINTS]);
+ }
+}
+
+/**
+ * st_entry_get_input_hints:
+ * @entry: a #StEntry
+ *
+ * Gets the value of the #StEntry:input-hints property.
+ *
+ * Returns: the input hints for the entry
+ */
+ClutterInputContentHintFlags
+st_entry_get_input_hints (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), 0);
+
+ priv = st_entry_get_instance_private (entry);
+ return clutter_text_get_input_hints (CLUTTER_TEXT (priv->entry));
+}
+
+static void
+_st_entry_icon_clicked_cb (ClutterClickAction *action,
+ ClutterActor *actor,
+ StEntry *entry)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ if (!clutter_actor_get_reactive (CLUTTER_ACTOR (entry)))
+ return;
+
+ if (actor == priv->primary_icon)
+ g_signal_emit (entry, entry_signals[PRIMARY_ICON_CLICKED], 0);
+ else
+ g_signal_emit (entry, entry_signals[SECONDARY_ICON_CLICKED], 0);
+}
+
+static void
+_st_entry_set_icon (StEntry *entry,
+ ClutterActor **icon,
+ ClutterActor *new_icon)
+{
+ if (*icon)
+ {
+ clutter_actor_remove_action_by_name (*icon, "entry-icon-action");
+ clutter_actor_remove_child (CLUTTER_ACTOR (entry), *icon);
+ *icon = NULL;
+ }
+
+ if (new_icon)
+ {
+ ClutterAction *action;
+
+ *icon = g_object_ref (new_icon);
+
+ clutter_actor_set_reactive (*icon, TRUE);
+ clutter_actor_add_child (CLUTTER_ACTOR (entry), *icon);
+
+ action = clutter_click_action_new ();
+ clutter_actor_add_action_with_name (*icon, "entry-icon-action", action);
+ g_signal_connect (action, "clicked",
+ G_CALLBACK (_st_entry_icon_clicked_cb), entry);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
+}
+
+/**
+ * st_entry_set_primary_icon:
+ * @entry: a #StEntry
+ * @icon: (nullable): a #ClutterActor
+ *
+ * Set the primary icon of the entry to @icon.
+ */
+void
+st_entry_set_primary_icon (StEntry *entry,
+ ClutterActor *icon)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+
+ if (priv->primary_icon == icon)
+ return;
+
+ _st_entry_set_icon (entry, &priv->primary_icon, icon);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PRIMARY_ICON]);
+}
+
+/**
+ * st_entry_get_primary_icon:
+ * @entry: a #StEntry
+ *
+ * Get the value of the #StEntry:primary-icon property.
+ *
+ * Returns: (nullable) (transfer none): a #ClutterActor
+ */
+ClutterActor *
+st_entry_get_primary_icon (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+ return priv->primary_icon;
+}
+
+/**
+ * st_entry_set_secondary_icon:
+ * @entry: a #StEntry
+ * @icon: (nullable): an #ClutterActor
+ *
+ * Set the secondary icon of the entry to @icon.
+ */
+void
+st_entry_set_secondary_icon (StEntry *entry,
+ ClutterActor *icon)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+
+ if (priv->secondary_icon == icon)
+ return;
+
+ _st_entry_set_icon (entry, &priv->secondary_icon, icon);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SECONDARY_ICON]);
+}
+
+/**
+ * st_entry_get_secondary_icon:
+ * @entry: a #StEntry
+ *
+ * Get the value of the #StEntry:secondary-icon property.
+ *
+ * Returns: (nullable) (transfer none): a #ClutterActor
+ */
+ClutterActor *
+st_entry_get_secondary_icon (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+ return priv->secondary_icon;
+}
+
+/**
+ * st_entry_set_hint_actor:
+ * @entry: a #StEntry
+ * @hint_actor: (nullable): a #ClutterActor
+ *
+ * Set the hint actor of the entry to @hint_actor.
+ */
+void
+st_entry_set_hint_actor (StEntry *entry,
+ ClutterActor *hint_actor)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = ST_ENTRY_PRIV (entry);
+
+ if (priv->hint_actor == hint_actor)
+ return;
+
+ if (priv->hint_actor != NULL)
+ {
+ clutter_actor_remove_child (CLUTTER_ACTOR (entry), priv->hint_actor);
+ priv->hint_actor = NULL;
+ }
+
+ if (hint_actor != NULL)
+ {
+ priv->hint_actor = hint_actor;
+ clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->hint_actor);
+ }
+
+ st_entry_update_hint_visibility (entry);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HINT_ACTOR]);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
+}
+
+/**
+ * st_entry_get_hint_actor:
+ * @entry: a #StEntry
+ *
+ * Get the value of the #StEntry:hint-actor property.
+ *
+ * Returns: (nullable) (transfer none): a #ClutterActor
+ */
+ClutterActor *
+st_entry_get_hint_actor (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+ return priv->hint_actor;
+}
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+#define ST_TYPE_ENTRY_ACCESSIBLE (st_entry_accessible_get_type ())
+#define ST_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessible))
+#define ST_IS_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ST_TYPE_ENTRY_ACCESSIBLE))
+#define ST_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
+#define ST_IS_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), ST_TYPE_ENTRY_ACCESSIBLE))
+#define ST_ENTRY_ACCESSIBLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
+
+typedef struct _StEntryAccessible StEntryAccessible;
+typedef struct _StEntryAccessibleClass StEntryAccessibleClass;
+
+struct _StEntryAccessible
+{
+ StWidgetAccessible parent;
+};
+
+struct _StEntryAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+G_DEFINE_TYPE (StEntryAccessible, st_entry_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
+
+static void
+st_entry_accessible_init (StEntryAccessible *self)
+{
+ /* initialization done on AtkObject->initialize */
+}
+
+static void
+st_entry_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_entry_accessible_parent_class)->initialize (obj, data);
+
+ /* StEntry is behaving as a ClutterText container */
+ atk_object_set_role (obj, ATK_ROLE_PANEL);
+}
+
+static gint
+st_entry_accessible_get_n_children (AtkObject *obj)
+{
+ StEntry *entry = NULL;
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), 0);
+
+ entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (entry == NULL)
+ return 0;
+
+ priv = st_entry_get_instance_private (entry);
+ if (priv->entry == NULL)
+ return 0;
+ else
+ return 1;
+}
+
+static AtkObject*
+st_entry_accessible_ref_child (AtkObject *obj,
+ gint i)
+{
+ StEntry *entry = NULL;
+ StEntryPrivate *priv;
+ AtkObject *result = NULL;
+
+ g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), NULL);
+ g_return_val_if_fail (i == 0, NULL);
+
+ entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (entry == NULL)
+ return NULL;
+
+ priv = st_entry_get_instance_private (entry);
+ if (priv->entry == NULL)
+ return NULL;
+
+ result = clutter_actor_get_accessible (priv->entry);
+ g_object_ref (result);
+
+ return result;
+}
+
+
+static void
+st_entry_accessible_class_init (StEntryAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ atk_class->initialize = st_entry_accessible_initialize;
+ atk_class->get_n_children = st_entry_accessible_get_n_children;
+ atk_class->ref_child= st_entry_accessible_ref_child;
+}
diff --git a/src/st/st-entry.h b/src/st/st-entry.h
new file mode 100644
index 0000000..2a05759
--- /dev/null
+++ b/src/st/st-entry.h
@@ -0,0 +1,79 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-entry.h: Plain entry actor
+ *
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_ENTRY_H__
+#define __ST_ENTRY_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-widget.h>
+
+#define ST_TYPE_ENTRY (st_entry_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StEntry, st_entry, ST, ENTRY, StWidget)
+
+struct _StEntryClass
+{
+ StWidgetClass parent_class;
+
+ /* signals */
+ void (*primary_icon_clicked) (StEntry *entry);
+ void (*secondary_icon_clicked) (StEntry *entry);
+};
+
+StWidget *st_entry_new (const gchar *text);
+const gchar *st_entry_get_text (StEntry *entry);
+void st_entry_set_text (StEntry *entry,
+ const gchar *text);
+ClutterActor *st_entry_get_clutter_text (StEntry *entry);
+
+void st_entry_set_hint_text (StEntry *entry,
+ const gchar *text);
+const gchar *st_entry_get_hint_text (StEntry *entry);
+
+void st_entry_set_input_purpose (StEntry *entry,
+ ClutterInputContentPurpose purpose);
+void st_entry_set_input_hints (StEntry *entry,
+ ClutterInputContentHintFlags hints);
+
+ClutterInputContentPurpose st_entry_get_input_purpose (StEntry *entry);
+ClutterInputContentHintFlags st_entry_get_input_hints (StEntry *entry);
+
+void st_entry_set_primary_icon (StEntry *entry,
+ ClutterActor *icon);
+ClutterActor * st_entry_get_primary_icon (StEntry *entry);
+
+void st_entry_set_secondary_icon (StEntry *entry,
+ ClutterActor *icon);
+ClutterActor * st_entry_get_secondary_icon (StEntry *entry);
+
+void st_entry_set_hint_actor (StEntry *entry,
+ ClutterActor *hint_actor);
+ClutterActor * st_entry_get_hint_actor (StEntry *entry);
+
+typedef void (*StEntryCursorFunc) (StEntry *entry, gboolean use_ibeam, gpointer data);
+void st_entry_set_cursor_func (StEntryCursorFunc func,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __ST_ENTRY_H__ */
diff --git a/src/st/st-focus-manager.c b/src/st/st-focus-manager.c
new file mode 100644
index 0000000..1ac6d28
--- /dev/null
+++ b/src/st/st-focus-manager.c
@@ -0,0 +1,256 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-focus-manager.c: Keyboard focus manager
+ *
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-focus-manager
+ * @short_description: Keyboard focus management
+ *
+ * #StFocusManager handles keyboard focus for all actors on the stage.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <clutter/clutter.h>
+
+#include "st-focus-manager.h"
+
+struct _StFocusManagerPrivate
+{
+ GHashTable *groups;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StFocusManager, st_focus_manager, G_TYPE_OBJECT)
+
+static void
+st_focus_manager_dispose (GObject *object)
+{
+ StFocusManager *manager = ST_FOCUS_MANAGER (object);
+
+ if (manager->priv->groups)
+ {
+ g_hash_table_destroy (manager->priv->groups);
+ manager->priv->groups = NULL;
+ }
+
+ G_OBJECT_CLASS (st_focus_manager_parent_class)->dispose (object);
+}
+
+static void
+st_focus_manager_class_init (StFocusManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = st_focus_manager_dispose;
+}
+
+static void
+st_focus_manager_init (StFocusManager *manager)
+{
+ manager->priv = st_focus_manager_get_instance_private (manager);
+ manager->priv->groups = g_hash_table_new (NULL, NULL);
+}
+
+static gboolean
+st_focus_manager_stage_event (ClutterActor *stage,
+ ClutterEvent *event,
+ gpointer user_data)
+{
+ StFocusManager *manager = user_data;
+ StDirectionType direction;
+ gboolean wrap_around = FALSE;
+ ClutterActor *focused, *group;
+
+ if (event->type != CLUTTER_KEY_PRESS)
+ return FALSE;
+
+ switch (event->key.keyval)
+ {
+ case CLUTTER_KEY_Up:
+ direction = ST_DIR_UP;
+ break;
+ case CLUTTER_KEY_Down:
+ direction = ST_DIR_DOWN;
+ break;
+ case CLUTTER_KEY_Left:
+ direction = ST_DIR_LEFT;
+ break;
+ case CLUTTER_KEY_Right:
+ direction = ST_DIR_RIGHT;
+ break;
+ case CLUTTER_KEY_Tab:
+ if (event->key.modifier_state & CLUTTER_SHIFT_MASK)
+ direction = ST_DIR_TAB_BACKWARD;
+ else
+ direction = ST_DIR_TAB_FORWARD;
+ wrap_around = TRUE;
+ break;
+ case CLUTTER_KEY_ISO_Left_Tab:
+ direction = ST_DIR_TAB_BACKWARD;
+ wrap_around = TRUE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ focused = clutter_stage_get_key_focus (CLUTTER_STAGE (stage));
+ if (!focused)
+ return FALSE;
+
+ for (group = focused; group != stage; group = clutter_actor_get_parent (group))
+ {
+ if (g_hash_table_lookup (manager->priv->groups, group))
+ {
+ return st_widget_navigate_focus (ST_WIDGET (group), focused,
+ direction, wrap_around);
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * st_focus_manager_get_for_stage:
+ * @stage: a #ClutterStage
+ *
+ * Gets the #StFocusManager for @stage, creating it if necessary.
+ *
+ * Returns: (transfer none): the focus manager for @stage
+ */
+StFocusManager *
+st_focus_manager_get_for_stage (ClutterStage *stage)
+{
+ StFocusManager *manager;
+
+ manager = g_object_get_data (G_OBJECT (stage), "st-focus-manager");
+ if (!manager)
+ {
+ manager = g_object_new (ST_TYPE_FOCUS_MANAGER, NULL);
+ g_object_set_data_full (G_OBJECT (stage), "st-focus-manager",
+ manager, g_object_unref);
+
+ g_signal_connect (stage, "event",
+ G_CALLBACK (st_focus_manager_stage_event), manager);
+ }
+
+ return manager;
+}
+
+static void
+remove_destroyed_group (ClutterActor *actor,
+ gpointer user_data)
+{
+ StFocusManager *manager = user_data;
+
+ st_focus_manager_remove_group (manager, ST_WIDGET (actor));
+}
+
+/**
+ * st_focus_manager_add_group:
+ * @manager: the #StFocusManager
+ * @root: the root container of the group
+ *
+ * Adds a new focus group to @manager. When the focus is in an actor
+ * that is a descendant of @root, @manager will handle moving focus
+ * from one actor to another within @root based on keyboard events.
+ */
+void
+st_focus_manager_add_group (StFocusManager *manager,
+ StWidget *root)
+{
+ gpointer count_p = g_hash_table_lookup (manager->priv->groups, root);
+ int count = count_p ? GPOINTER_TO_INT (count_p) : 0;
+
+ g_signal_connect (root, "destroy",
+ G_CALLBACK (remove_destroyed_group),
+ manager);
+ g_hash_table_insert (manager->priv->groups, root, GINT_TO_POINTER (++count));
+}
+
+/**
+ * st_focus_manager_remove_group:
+ * @manager: the #StFocusManager
+ * @root: the root container of the group
+ *
+ * Removes the group rooted at @root from @manager
+ */
+void
+st_focus_manager_remove_group (StFocusManager *manager,
+ StWidget *root)
+{
+ gpointer count_p = g_hash_table_lookup (manager->priv->groups, root);
+ int count = count_p ? GPOINTER_TO_INT (count_p) : 0;
+
+ if (count == 0)
+ return;
+ if (count == 1)
+ g_hash_table_remove (manager->priv->groups, root);
+ else
+ g_hash_table_insert (manager->priv->groups, root, GINT_TO_POINTER(--count));
+}
+
+/**
+ * st_focus_manager_get_group:
+ * @manager: the #StFocusManager
+ * @widget: an #StWidget
+ *
+ * Checks if @widget is inside a focus group, and if so, returns
+ * the root of that group.
+ *
+ * Returns: (transfer none): the focus group root, or %NULL if
+ * @widget is not in a focus group
+ */
+StWidget *
+st_focus_manager_get_group (StFocusManager *manager,
+ StWidget *widget)
+{
+ ClutterActor *actor = CLUTTER_ACTOR (widget);
+
+ while (actor && !g_hash_table_lookup (manager->priv->groups, actor))
+ actor = clutter_actor_get_parent (actor);
+
+ return ST_WIDGET (actor);
+}
+
+/**
+ * st_focus_manager_navigate_from_event:
+ * @manager: the #StFocusManager
+ * @event: a #ClutterEvent
+ *
+ * Try to navigate from @event as if it bubbled all the way up to
+ * the stage. This is useful in complex event handling situations
+ * where you want key navigation, but a parent might be stopping
+ * the key navigation event from bubbling all the way up to the stage.
+ *
+ * Returns: Whether a new actor was navigated to
+ */
+gboolean
+st_focus_manager_navigate_from_event (StFocusManager *manager,
+ ClutterEvent *event)
+{
+ ClutterActor *stage;
+
+ if (event->type != CLUTTER_KEY_PRESS)
+ return FALSE;
+
+ stage = CLUTTER_ACTOR (event->key.stage);
+ return st_focus_manager_stage_event (stage, event, manager);
+}
diff --git a/src/st/st-focus-manager.h b/src/st/st-focus-manager.h
new file mode 100644
index 0000000..ba8442b
--- /dev/null
+++ b/src/st/st-focus-manager.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-focus-manager.h: Keyboard focus manager
+ *
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_FOCUS_MANAGER_H__
+#define __ST_FOCUS_MANAGER_H__
+
+#include <st/st-types.h>
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_FOCUS_MANAGER (st_focus_manager_get_type ())
+G_DECLARE_FINAL_TYPE (StFocusManager, st_focus_manager, ST, FOCUS_MANAGER, GObject)
+
+typedef struct _StFocusManager StFocusManager;
+typedef struct _StFocusManagerPrivate StFocusManagerPrivate;
+
+/**
+ * StFocusManager:
+ *
+ * The #StFocusManager struct contains only private data
+ */
+struct _StFocusManager
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ StFocusManagerPrivate *priv;
+};
+
+StFocusManager *st_focus_manager_get_for_stage (ClutterStage *stage);
+
+void st_focus_manager_add_group (StFocusManager *manager,
+ StWidget *root);
+void st_focus_manager_remove_group (StFocusManager *manager,
+ StWidget *root);
+StWidget *st_focus_manager_get_group (StFocusManager *manager,
+ StWidget *widget);
+gboolean st_focus_manager_navigate_from_event (StFocusManager *manager,
+ ClutterEvent *event);
+
+G_END_DECLS
+
+#endif /* __ST_FOCUS_MANAGER_H__ */
diff --git a/src/st/st-generic-accessible.c b/src/st/st-generic-accessible.c
new file mode 100644
index 0000000..e6cb393
--- /dev/null
+++ b/src/st/st-generic-accessible.c
@@ -0,0 +1,246 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-generic-accessible.c: generic accessible
+ *
+ * Copyright 2013 Igalia, S.L.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-generic-accessible
+ * @short_description: An accessible class with signals for
+ * implementing specific Atk interfaces
+ *
+ * #StGenericAccessible is mainly a workaround for the current lack of
+ * of a proper support for GValue at javascript. See bug#703412 for
+ * more information. We implement the accessible interfaces, but proxy
+ * the virtual functions into signals, which gjs can catch.
+ *
+ * #StGenericAccessible is an #StWidgetAccessible
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "st-generic-accessible.h"
+
+static void atk_value_iface_init (AtkValueIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE(StGenericAccessible,
+ st_generic_accessible,
+ ST_TYPE_WIDGET_ACCESSIBLE,
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE,
+ atk_value_iface_init));
+/* Signals */
+enum
+{
+ GET_CURRENT_VALUE,
+ GET_MAXIMUM_VALUE,
+ GET_MINIMUM_VALUE,
+ SET_CURRENT_VALUE,
+ GET_MINIMUM_INCREMENT,
+ LAST_SIGNAL
+};
+
+static guint st_generic_accessible_signals [LAST_SIGNAL] = { 0 };
+
+static void
+st_generic_accessible_init (StGenericAccessible *accessible)
+{
+}
+
+static void
+st_generic_accessible_class_init (StGenericAccessibleClass *klass)
+{
+ /**
+ * StGenericAccessible::get-current-value:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_current_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: value of the current element.
+ */
+ st_generic_accessible_signals[GET_CURRENT_VALUE] =
+ g_signal_new ("get-current-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::get-maximum-value:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_maximum_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: maximum value of the accessible.
+ */
+ st_generic_accessible_signals[GET_MAXIMUM_VALUE] =
+ g_signal_new ("get-maximum-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::get-minimum-value:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_current_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: minimum value of the accessible.
+ */
+ st_generic_accessible_signals[GET_MINIMUM_VALUE] =
+ g_signal_new ("get-minimum-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::get-minimum-increment:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_minimum_increment() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: value of the current element.
+ */
+ st_generic_accessible_signals[GET_MINIMUM_INCREMENT] =
+ g_signal_new ("get-minimum-increment",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::set-current-value:
+ * @self: the #StGenericAccessible
+ * @new_value: the new value for the accessible
+ *
+ * Emitted when atk_value_set_current_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ */
+ st_generic_accessible_signals[SET_CURRENT_VALUE] =
+ g_signal_new ("set-current-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_DOUBLE);
+
+}
+
+static void
+st_generic_accessible_get_current_value (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_CURRENT_VALUE], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static void
+st_generic_accessible_get_maximum_value (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_MAXIMUM_VALUE], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static void
+st_generic_accessible_get_minimum_value (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_MINIMUM_VALUE], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static void
+st_generic_accessible_get_minimum_increment (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_MINIMUM_INCREMENT], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static gboolean
+st_generic_accessible_set_current_value (AtkValue *obj,
+ const GValue *value)
+{
+ gdouble current_value = 0;
+
+ current_value = g_value_get_double (value);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[SET_CURRENT_VALUE], 0, current_value);
+
+ return TRUE; // we assume that the value was properly set
+}
+
+static void
+atk_value_iface_init (AtkValueIface *iface)
+{
+ iface->get_current_value = st_generic_accessible_get_current_value;
+ iface->get_maximum_value = st_generic_accessible_get_maximum_value;
+ iface->get_minimum_value = st_generic_accessible_get_minimum_value;
+ iface->get_minimum_increment = st_generic_accessible_get_minimum_increment;
+ iface->set_current_value = st_generic_accessible_set_current_value;
+}
+
+/**
+ * st_generic_accessible_new_for_actor:
+ * @actor: a #Clutter Actor
+ *
+ * Create a new #StGenericAccessible for @actor.
+ *
+ * This is useful only for custom widgets that need a proxy for #AtkObject.
+ *
+ * Returns: (transfer full): a new #AtkObject
+ */
+AtkObject*
+st_generic_accessible_new_for_actor (ClutterActor *actor)
+{
+ AtkObject *accessible = NULL;
+
+ g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
+
+ accessible = ATK_OBJECT (g_object_new (ST_TYPE_GENERIC_ACCESSIBLE,
+ NULL));
+ atk_object_initialize (accessible, actor);
+
+ return accessible;
+}
diff --git a/src/st/st-generic-accessible.h b/src/st/st-generic-accessible.h
new file mode 100644
index 0000000..99a6a71
--- /dev/null
+++ b/src/st/st-generic-accessible.h
@@ -0,0 +1,62 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-generic-accessible.h: generic accessible
+ *
+ * Copyright 2013 Igalia, S.L.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_GENERIC_ACCESSIBLE_H__
+#define __ST_GENERIC_ACCESSIBLE_H__
+
+#include <clutter/clutter.h>
+#include <st/st-widget-accessible.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_GENERIC_ACCESSIBLE (st_generic_accessible_get_type ())
+#define ST_GENERIC_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_GENERIC_ACCESSIBLE, StGenericAccessible))
+#define ST_GENERIC_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_GENERIC_ACCESSIBLE, StGenericAccessibleClass))
+#define ST_IS_GENERIC_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_GENERIC_ACCESSIBLE))
+#define ST_IS_GENERIC_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_GENERIC_ACCESSIBLE))
+#define ST_GENERIC_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_GENERIC_ACCESSIBLE, StGenericAccessibleClass))
+
+typedef struct _StGenericAccessible StGenericAccessible;
+typedef struct _StGenericAccessibleClass StGenericAccessibleClass;
+
+typedef struct _StGenericAccessiblePrivate StGenericAccessiblePrivate;
+
+struct _StGenericAccessible
+{
+ StWidgetAccessible parent;
+
+ StGenericAccessiblePrivate *priv;
+};
+
+struct _StGenericAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+GType st_generic_accessible_get_type (void) G_GNUC_CONST;
+
+AtkObject* st_generic_accessible_new_for_actor (ClutterActor *actor);
+
+G_END_DECLS
+
+#endif /* __ST_GENERIC_ACCESSIBLE_H__ */
diff --git a/src/st/st-icon-colors.c b/src/st/st-icon-colors.c
new file mode 100644
index 0000000..c6a082a
--- /dev/null
+++ b/src/st/st-icon-colors.c
@@ -0,0 +1,133 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-icon-colors.c: Colors for colorizing a symbolic icon
+ *
+ * Copyright 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-icon-colors.h"
+
+/**
+ * st_icon_colors_new:
+ *
+ * Creates a new #StIconColors. All colors are initialized to transparent black.
+ *
+ * Returns: a newly created #StIconColors. Free with st_icon_colors_unref()
+ */
+StIconColors *
+st_icon_colors_new (void)
+{
+ StIconColors *colors;
+
+ colors = g_new0 (StIconColors, 1);
+ colors->ref_count = 1;
+
+ return colors;
+}
+
+/**
+ * st_icon_colors_ref:
+ * @colors: a #StIconColors
+ *
+ * Atomically increments the reference count of @colors by one.
+ *
+ * Returns: the passed in #StIconColors.
+ */
+StIconColors *
+st_icon_colors_ref (StIconColors *colors)
+{
+ g_return_val_if_fail (colors != NULL, NULL);
+ g_return_val_if_fail (colors->ref_count > 0, colors);
+
+ g_atomic_int_inc ((volatile int *)&colors->ref_count);
+ return colors;
+}
+
+/**
+ * st_icon_colors_unref:
+ * @colors: a #StIconColors
+ *
+ * Atomically decrements the reference count of @colors by one.
+ * If the reference count drops to 0, all memory allocated by the
+ * #StIconColors is released.
+ */
+void
+st_icon_colors_unref (StIconColors *colors)
+{
+ g_return_if_fail (colors != NULL);
+ g_return_if_fail (colors->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test ((volatile int *)&colors->ref_count))
+ g_free (colors);
+}
+
+/**
+ * st_icon_colors_copy:
+ * @colors: a #StIconColors
+ *
+ * Creates a new StIconColors structure that is a copy of the passed
+ * in @colors. You would use this function instead of st_icon_colors_ref()
+ * if you were planning to change colors in the result.
+ *
+ * Returns: a newly created #StIconColors.
+ */
+StIconColors *
+st_icon_colors_copy (StIconColors *colors)
+{
+ StIconColors *copy;
+
+ g_return_val_if_fail (colors != NULL, NULL);
+
+ copy = st_icon_colors_new ();
+
+ copy->foreground = colors->foreground;
+ copy->warning = colors->warning;
+ copy->error = colors->error;
+ copy->success = colors->success;
+
+ return copy;
+}
+
+/**
+ * st_icon_colors_equal:
+ * @colors: a #StIconColors
+ * @other: another #StIconColors
+ *
+ * Check if two #StIconColors objects are identical.
+ *
+ * Returns: %TRUE if the #StIconColors are equal
+ */
+gboolean
+st_icon_colors_equal (StIconColors *colors,
+ StIconColors *other)
+{
+ if (colors == other)
+ return TRUE;
+
+ if (colors == NULL || other == NULL)
+ return FALSE;
+
+ return clutter_color_equal (&colors->foreground, &other->foreground) &&
+ clutter_color_equal (&colors->warning, &other->warning) &&
+ clutter_color_equal (&colors->error, &other->error) &&
+ clutter_color_equal (&colors->success, &other->success);
+}
+
+G_DEFINE_BOXED_TYPE (StIconColors,
+ st_icon_colors,
+ st_icon_colors_ref,
+ st_icon_colors_unref)
diff --git a/src/st/st-icon-colors.h b/src/st/st-icon-colors.h
new file mode 100644
index 0000000..e994a75
--- /dev/null
+++ b/src/st/st-icon-colors.h
@@ -0,0 +1,43 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __ST_ICON_COLORS__
+#define __ST_ICON_COLORS__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_ICON_COLORS (st_icon_colors_get_type ())
+
+typedef struct _StIconColors StIconColors;
+
+/**
+ * StIconColors:
+ * @foreground: foreground color
+ * @warning: color indicating a warning state
+ * @error: color indicating an error state
+ * @success: color indicating a successful operation
+ *
+ * The #StIconColors structure encapsulates colors for colorizing a symbolic
+ * icon.
+ */
+struct _StIconColors {
+ volatile guint ref_count;
+
+ ClutterColor foreground;
+ ClutterColor warning;
+ ClutterColor error;
+ ClutterColor success;
+};
+
+GType st_icon_colors_get_type (void) G_GNUC_CONST;
+
+StIconColors *st_icon_colors_new (void);
+StIconColors *st_icon_colors_ref (StIconColors *colors);
+void st_icon_colors_unref (StIconColors *colors);
+StIconColors *st_icon_colors_copy (StIconColors *colors);
+gboolean st_icon_colors_equal (StIconColors *colors,
+ StIconColors *other);
+
+G_END_DECLS
+
+#endif /* __ST_ICON_COLORS__ */
diff --git a/src/st/st-icon.c b/src/st/st-icon.c
new file mode 100644
index 0000000..6009afe
--- /dev/null
+++ b/src/st/st-icon.c
@@ -0,0 +1,833 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-icon.c: icon widget
+ *
+ * Copyright 2009, 2010 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-icon
+ * @short_description: a simple styled icon actor
+ *
+ * #StIcon is a simple styled texture actor that displays an image from
+ * a stylesheet.
+ */
+
+#include "st-enum-types.h"
+#include "st-icon.h"
+#include "st-texture-cache.h"
+#include "st-theme-context.h"
+#include "st-private.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_GICON,
+ PROP_FALLBACK_GICON,
+
+ PROP_ICON_NAME,
+ PROP_ICON_SIZE,
+ PROP_FALLBACK_ICON_NAME,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+struct _StIconPrivate
+{
+ ClutterActor *icon_texture;
+ ClutterActor *pending_texture;
+ gulong opacity_handler_id;
+
+ GIcon *gicon;
+ gint prop_icon_size; /* icon size set as property */
+ gint theme_icon_size; /* icon size from theme node */
+ gint icon_size; /* icon size we are using */
+ GIcon *fallback_gicon;
+ gboolean needs_update;
+
+ StIconColors *colors;
+
+ CoglPipeline *shadow_pipeline;
+ StShadow *shadow_spec;
+ graphene_size_t shadow_size;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StIcon, st_icon, ST_TYPE_WIDGET)
+
+static void st_icon_update (StIcon *icon);
+static gboolean st_icon_update_icon_size (StIcon *icon);
+static void st_icon_update_shadow_pipeline (StIcon *icon);
+static void st_icon_clear_shadow_pipeline (StIcon *icon);
+
+static GIcon *default_gicon = NULL;
+
+#define IMAGE_MISSING_ICON_NAME "image-missing"
+#define DEFAULT_ICON_SIZE 48
+
+static void
+st_icon_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StIcon *icon = ST_ICON (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_GICON:
+ st_icon_set_gicon (icon, g_value_get_object (value));
+ break;
+
+ case PROP_FALLBACK_GICON:
+ st_icon_set_fallback_gicon (icon, g_value_get_object (value));
+ break;
+
+ case PROP_ICON_NAME:
+ st_icon_set_icon_name (icon, g_value_get_string (value));
+ break;
+
+ case PROP_ICON_SIZE:
+ st_icon_set_icon_size (icon, g_value_get_int (value));
+ break;
+
+ case PROP_FALLBACK_ICON_NAME:
+ st_icon_set_fallback_icon_name (icon, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_icon_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StIcon *icon = ST_ICON (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_GICON:
+ g_value_set_object (value, st_icon_get_gicon (icon));
+ break;
+
+ case PROP_FALLBACK_GICON:
+ g_value_set_object (value, st_icon_get_fallback_gicon (icon));
+ break;
+
+ case PROP_ICON_NAME:
+ g_value_set_string (value, st_icon_get_icon_name (icon));
+ break;
+
+ case PROP_ICON_SIZE:
+ g_value_set_int (value, st_icon_get_icon_size (icon));
+ break;
+
+ case PROP_FALLBACK_ICON_NAME:
+ g_value_set_string (value, st_icon_get_fallback_icon_name (icon));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_icon_dispose (GObject *gobject)
+{
+ StIconPrivate *priv = ST_ICON (gobject)->priv;
+
+ if (priv->icon_texture)
+ {
+ clutter_actor_destroy (priv->icon_texture);
+ priv->icon_texture = NULL;
+ }
+
+ if (priv->pending_texture)
+ {
+ clutter_actor_destroy (priv->pending_texture);
+ g_object_unref (priv->pending_texture);
+ priv->pending_texture = NULL;
+ }
+
+ g_clear_object (&priv->gicon);
+ g_clear_object (&priv->fallback_gicon);
+ g_clear_pointer (&priv->colors, st_icon_colors_unref);
+ g_clear_pointer (&priv->shadow_pipeline, cogl_object_unref);
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+
+ G_OBJECT_CLASS (st_icon_parent_class)->dispose (gobject);
+}
+
+static void
+st_icon_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StIcon *icon = ST_ICON (actor);
+ StIconPrivate *priv = icon->priv;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->icon_texture)
+ {
+ st_icon_update_shadow_pipeline (icon);
+
+ if (priv->shadow_pipeline)
+ {
+ ClutterActorBox allocation;
+ CoglFramebuffer *framebuffer;
+
+ clutter_actor_get_allocation_box (priv->icon_texture, &allocation);
+ framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+ _st_paint_shadow_with_opacity (priv->shadow_spec,
+ framebuffer,
+ priv->shadow_pipeline,
+ &allocation,
+ clutter_actor_get_paint_opacity (priv->icon_texture));
+ }
+
+ clutter_actor_paint (priv->icon_texture, paint_context);
+ }
+}
+
+static void
+st_icon_style_changed (StWidget *widget)
+{
+ StIcon *self = ST_ICON (widget);
+ StThemeNode *theme_node = st_widget_get_theme_node (widget);
+ StIconPrivate *priv = self->priv;
+ gboolean should_update = FALSE;
+ g_autoptr(StShadow) shadow_spec = NULL;
+ StIconColors *colors;
+
+ shadow_spec = st_theme_node_get_shadow (theme_node, "icon-shadow");
+
+ if (shadow_spec && shadow_spec->inset)
+ {
+ g_warning ("The icon-shadow property does not support inset shadows");
+ g_clear_pointer (&shadow_spec, st_shadow_unref);
+ }
+
+ if ((shadow_spec && priv->shadow_spec && !st_shadow_equal (shadow_spec, priv->shadow_spec)) ||
+ (shadow_spec && !priv->shadow_spec) || (!shadow_spec && priv->shadow_spec))
+ {
+ st_icon_clear_shadow_pipeline (self);
+
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+ priv->shadow_spec = g_steal_pointer (&shadow_spec);
+
+ should_update = TRUE;
+ }
+
+ colors = st_theme_node_get_icon_colors (theme_node);
+
+ if ((colors && priv->colors && !st_icon_colors_equal (colors, priv->colors)) ||
+ (colors && !priv->colors) || (!colors && priv->colors))
+ {
+ g_clear_pointer (&priv->colors, st_icon_colors_unref);
+ priv->colors = st_icon_colors_ref (colors);
+
+ should_update = TRUE;
+ }
+
+ priv->theme_icon_size = (int)(0.5 + st_theme_node_get_length (theme_node, "icon-size"));
+
+ should_update |= st_icon_update_icon_size (self);
+
+ if (priv->needs_update || should_update)
+ st_icon_update (self);
+
+ ST_WIDGET_CLASS (st_icon_parent_class)->style_changed (widget);
+}
+
+static void
+st_icon_resource_scale_changed (ClutterActor *actor)
+{
+ st_icon_update (ST_ICON (actor));
+
+ if (CLUTTER_ACTOR_CLASS (st_icon_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_icon_parent_class)->resource_scale_changed (actor);
+}
+
+static void
+st_icon_class_init (StIconClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ object_class->get_property = st_icon_get_property;
+ object_class->set_property = st_icon_set_property;
+ object_class->dispose = st_icon_dispose;
+
+ actor_class->paint = st_icon_paint;
+
+ widget_class->style_changed = st_icon_style_changed;
+ actor_class->resource_scale_changed = st_icon_resource_scale_changed;
+
+ /**
+ * StIcon:gicon:
+ *
+ * The #GIcon being displayed by this #StIcon.
+ */
+ props[PROP_GICON] =
+ g_param_spec_object ("gicon",
+ "GIcon",
+ "The GIcon shown by this icon actor",
+ G_TYPE_ICON,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:fallback-gicon:
+ *
+ * The fallback #GIcon to display if #StIcon:gicon fails to load.
+ */
+ props[PROP_FALLBACK_GICON] =
+ g_param_spec_object ("fallback-gicon",
+ "Fallback GIcon",
+ "The fallback GIcon shown if the normal icon fails to load",
+ G_TYPE_ICON,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:icon-name:
+ *
+ * The name of the icon if the icon being displayed is a #GThemedIcon.
+ */
+ props[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon name",
+ "An icon name",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:icon-size:
+ *
+ * The size of the icon, if greater than `0`. Other the icon size is derived
+ * from the current style.
+ */
+ props[PROP_ICON_SIZE] =
+ g_param_spec_int ("icon-size",
+ "Icon size",
+ "The size if the icon, if positive. Otherwise the size will be derived from the current style",
+ -1, G_MAXINT, -1,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:fallback-icon-name:
+ *
+ * The fallback icon name of the #StIcon. See st_icon_set_fallback_icon_name()
+ * for details.
+ */
+ props[PROP_FALLBACK_ICON_NAME] =
+ g_param_spec_string ("fallback-icon-name",
+ "Fallback icon name",
+ "A fallback icon name",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_icon_init (StIcon *self)
+{
+ ClutterLayoutManager *layout_manager;
+
+ if (G_UNLIKELY (default_gicon == NULL))
+ default_gicon = g_themed_icon_new (IMAGE_MISSING_ICON_NAME);
+
+ self->priv = st_icon_get_instance_private (self);
+
+ layout_manager = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL,
+ CLUTTER_BIN_ALIGNMENT_FILL);
+ clutter_actor_set_layout_manager (CLUTTER_ACTOR (self), layout_manager);
+
+ /* Set the icon size to -1 here to make sure we apply the scale to the
+ * default size on the first "style-changed" signal. */
+ self->priv->icon_size = -1;
+ self->priv->prop_icon_size = -1;
+
+ self->priv->shadow_pipeline = NULL;
+}
+
+static void
+st_icon_clear_shadow_pipeline (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+
+ g_clear_pointer (&priv->shadow_pipeline, cogl_object_unref);
+ graphene_size_init (&priv->shadow_size, 0, 0);
+}
+
+static void
+st_icon_update_shadow_pipeline (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+
+ if (priv->icon_texture && priv->shadow_spec)
+ {
+ ClutterActorBox box;
+ float width, height;
+
+ clutter_actor_get_allocation_box (CLUTTER_ACTOR (priv->icon_texture),
+ &box);
+ clutter_actor_box_get_size (&box, &width, &height);
+
+ if (priv->shadow_pipeline == NULL ||
+ priv->shadow_size.width != width ||
+ priv->shadow_size.height != height)
+ {
+ st_icon_clear_shadow_pipeline (icon);
+
+ priv->shadow_pipeline =
+ _st_create_shadow_pipeline_from_actor (priv->shadow_spec,
+ priv->icon_texture);
+
+ if (priv->shadow_pipeline)
+ graphene_size_init (&priv->shadow_size, width, height);
+ }
+ }
+}
+
+static void
+on_content_changed (ClutterActor *actor,
+ GParamSpec *pspec,
+ StIcon *icon)
+{
+ st_icon_clear_shadow_pipeline (icon);
+}
+
+static void
+st_icon_finish_update (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+
+ if (priv->icon_texture)
+ {
+ clutter_actor_destroy (priv->icon_texture);
+ priv->icon_texture = NULL;
+ }
+
+ if (priv->pending_texture)
+ {
+ priv->icon_texture = priv->pending_texture;
+ priv->pending_texture = NULL;
+ clutter_actor_set_x_align (priv->icon_texture, CLUTTER_ACTOR_ALIGN_CENTER);
+ clutter_actor_set_y_align (priv->icon_texture, CLUTTER_ACTOR_ALIGN_CENTER);
+ clutter_actor_add_child (CLUTTER_ACTOR (icon), priv->icon_texture);
+
+ /* Remove the temporary ref we added */
+ g_object_unref (priv->icon_texture);
+
+ st_icon_clear_shadow_pipeline (icon);
+
+ g_signal_connect_object (priv->icon_texture, "notify::content",
+ G_CALLBACK (on_content_changed), icon, 0);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (icon));
+}
+
+static void
+opacity_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StIcon *icon = user_data;
+ StIconPrivate *priv = icon->priv;
+
+ g_clear_signal_handler (&priv->opacity_handler_id, priv->pending_texture);
+
+ st_icon_finish_update (icon);
+}
+
+static void
+st_icon_update (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+ StThemeNode *theme_node;
+ StTextureCache *cache;
+ gint paint_scale;
+ ClutterActor *stage;
+ StThemeContext *context;
+ float resource_scale;
+
+ if (priv->pending_texture)
+ {
+ clutter_actor_destroy (priv->pending_texture);
+ g_object_unref (priv->pending_texture);
+ priv->pending_texture = NULL;
+ priv->opacity_handler_id = 0;
+ }
+
+ if (priv->gicon == NULL && priv->fallback_gicon == NULL)
+ {
+ g_clear_pointer (&priv->icon_texture, clutter_actor_destroy);
+ return;
+ }
+
+ priv->needs_update = TRUE;
+
+ theme_node = st_widget_peek_theme_node (ST_WIDGET (icon));
+ if (theme_node == NULL)
+ return;
+
+ if (priv->icon_size <= 0)
+ return;
+
+ resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (icon));
+
+ stage = clutter_actor_get_stage (CLUTTER_ACTOR (icon));
+ context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
+ g_object_get (context, "scale-factor", &paint_scale, NULL);
+
+ cache = st_texture_cache_get_default ();
+
+ if (priv->gicon != NULL)
+ priv->pending_texture = st_texture_cache_load_gicon (cache,
+ theme_node,
+ priv->gicon,
+ priv->icon_size / paint_scale,
+ paint_scale,
+ resource_scale);
+
+ if (priv->pending_texture == NULL && priv->fallback_gicon != NULL)
+ priv->pending_texture = st_texture_cache_load_gicon (cache,
+ theme_node,
+ priv->fallback_gicon,
+ priv->icon_size / paint_scale,
+ paint_scale,
+ resource_scale);
+
+ if (priv->pending_texture == NULL)
+ priv->pending_texture = st_texture_cache_load_gicon (cache,
+ theme_node,
+ default_gicon,
+ priv->icon_size / paint_scale,
+ paint_scale,
+ resource_scale);
+ priv->needs_update = FALSE;
+
+ if (priv->pending_texture)
+ {
+ g_object_ref_sink (priv->pending_texture);
+
+ if (clutter_actor_get_opacity (priv->pending_texture) != 0 || priv->icon_texture == NULL)
+ {
+ /* This icon is ready for showing, or nothing else is already showing */
+ st_icon_finish_update (icon);
+ }
+ else
+ {
+ /* Will be shown when fully loaded */
+ priv->opacity_handler_id = g_signal_connect_object (priv->pending_texture, "notify::opacity", G_CALLBACK (opacity_changed_cb), icon, 0);
+ }
+ }
+ else if (priv->icon_texture)
+ {
+ clutter_actor_destroy (priv->icon_texture);
+ priv->icon_texture = NULL;
+ }
+}
+
+static gboolean
+st_icon_update_icon_size (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+ int new_size;
+ gint scale = 1;
+ ClutterActor *stage;
+ StThemeContext *context;
+
+ stage = clutter_actor_get_stage (CLUTTER_ACTOR (icon));
+ if (stage != NULL)
+ {
+ context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
+ g_object_get (context, "scale-factor", &scale, NULL);
+ }
+
+ if (priv->prop_icon_size > 0)
+ new_size = priv->prop_icon_size * scale;
+ else if (priv->theme_icon_size > 0)
+ new_size = priv->theme_icon_size;
+ else
+ new_size = DEFAULT_ICON_SIZE * scale;
+
+ if (new_size != priv->icon_size)
+ {
+ priv->icon_size = new_size;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_icon_new:
+ *
+ * Create a newly allocated #StIcon.
+ *
+ * Returns: A newly allocated #StIcon
+ */
+ClutterActor *
+st_icon_new (void)
+{
+ return g_object_new (ST_TYPE_ICON, NULL);
+}
+
+/**
+ * st_icon_get_icon_name:
+ * @icon: an #StIcon
+ *
+ * This is a convenience method to get the icon name of the current icon, if it
+ * is currenyly a #GThemedIcon, or %NULL otherwise.
+ *
+ * Returns: (transfer none) (nullable): The name of the icon or %NULL
+ */
+const gchar *
+st_icon_get_icon_name (StIcon *icon)
+{
+ StIconPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ priv = icon->priv;
+
+ if (priv->gicon && G_IS_THEMED_ICON (priv->gicon))
+ return g_themed_icon_get_names (G_THEMED_ICON (priv->gicon)) [0];
+ else
+ return NULL;
+}
+
+/**
+ * st_icon_set_icon_name:
+ * @icon: an #StIcon
+ * @icon_name: (nullable): the name of the icon
+ *
+ * This is a convenience method to set the #GIcon to a #GThemedIcon created
+ * using the given icon name. If @icon_name is an empty string, %NULL or
+ * fails to load, the fallback icon will be shown.
+ */
+void
+st_icon_set_icon_name (StIcon *icon,
+ const gchar *icon_name)
+{
+ g_autoptr(GIcon) gicon = NULL;
+
+ g_return_if_fail (ST_IS_ICON (icon));
+
+ if (g_strcmp0 (icon_name, st_icon_get_icon_name (icon)) == 0)
+ return;
+
+ if (icon_name && *icon_name)
+ gicon = g_themed_icon_new_with_default_fallbacks (icon_name);
+
+ g_object_freeze_notify (G_OBJECT (icon));
+
+ st_icon_set_gicon (icon, gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_ICON_NAME]);
+
+ g_object_thaw_notify (G_OBJECT (icon));
+}
+
+/**
+ * st_icon_get_gicon:
+ * @icon: an #StIcon
+ *
+ * Gets the current #GIcon in use.
+ *
+ * Returns: (nullable) (transfer none): The current #GIcon, if set, otherwise %NULL
+ */
+GIcon *
+st_icon_get_gicon (StIcon *icon)
+{
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ return icon->priv->gicon;
+}
+
+/**
+ * st_icon_set_gicon:
+ * @icon: an #StIcon
+ * @gicon: (nullable): a #GIcon
+ *
+ * Sets a #GIcon to show for the icon. If @gicon is %NULL or fails to load,
+ * the fallback icon set using st_icon_set_fallback_icon() will be shown.
+ */
+void
+st_icon_set_gicon (StIcon *icon, GIcon *gicon)
+{
+ g_return_if_fail (ST_IS_ICON (icon));
+ g_return_if_fail (gicon == NULL || G_IS_ICON (gicon));
+
+ if (g_icon_equal (icon->priv->gicon, gicon)) /* do nothing */
+ return;
+
+ g_set_object (&icon->priv->gicon, gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_GICON]);
+
+ st_icon_update (icon);
+}
+
+/**
+ * st_icon_get_fallback_gicon:
+ * @icon: a #StIcon
+ *
+ * Gets the currently set fallback #GIcon.
+ *
+ * Returns: (transfer none): The fallback #GIcon, if set, otherwise %NULL
+ */
+GIcon *
+st_icon_get_fallback_gicon (StIcon *icon)
+{
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ return icon->priv->fallback_gicon;
+}
+
+/**
+ * st_icon_set_fallback_gicon:
+ * @icon: a #StIcon
+ * @fallback_gicon: (nullable): the fallback #GIcon
+ *
+ * Sets a fallback #GIcon to show if the normal icon fails to load.
+ * If @fallback_gicon is %NULL or fails to load, the icon is unset and no
+ * texture will be visible for the fallback icon.
+ */
+void
+st_icon_set_fallback_gicon (StIcon *icon,
+ GIcon *fallback_gicon)
+{
+ g_return_if_fail (ST_IS_ICON (icon));
+ g_return_if_fail (fallback_gicon == NULL || G_IS_ICON (fallback_gicon));
+
+ if (g_icon_equal (icon->priv->fallback_gicon, fallback_gicon))
+ return;
+
+ g_set_object (&icon->priv->fallback_gicon, fallback_gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_FALLBACK_GICON]);
+
+ st_icon_update (icon);
+}
+
+/**
+ * st_icon_get_icon_size:
+ * @icon: an #StIcon
+ *
+ * Gets the explicit size set using st_icon_set_icon_size() for the icon.
+ * This is not necessarily the size that the icon will be displayed at.
+ *
+ * Returns: The explicitly set size, or -1 if no size has been set
+ */
+gint
+st_icon_get_icon_size (StIcon *icon)
+{
+ g_return_val_if_fail (ST_IS_ICON (icon), -1);
+
+ return icon->priv->prop_icon_size;
+}
+
+/**
+ * st_icon_set_icon_size:
+ * @icon: an #StIcon
+ * @size: if positive, the new size, otherwise the size will be
+ * derived from the current style
+ *
+ * Sets an explicit size for the icon. Setting @size to -1 will use the size
+ * defined by the current style or the default icon size.
+ */
+void
+st_icon_set_icon_size (StIcon *icon,
+ gint size)
+{
+ StIconPrivate *priv;
+
+ g_return_if_fail (ST_IS_ICON (icon));
+
+ priv = icon->priv;
+ if (priv->prop_icon_size != size)
+ {
+ priv->prop_icon_size = size;
+ if (st_icon_update_icon_size (icon))
+ st_icon_update (icon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_ICON_SIZE]);
+ }
+}
+
+/**
+ * st_icon_get_fallback_icon_name:
+ * @icon: an #StIcon
+ *
+ * This is a convenience method to get the icon name of the fallback
+ * #GThemedIcon that is currently set.
+ *
+ * Returns: (transfer none): The name of the icon or %NULL if no icon is set
+ */
+const gchar *
+st_icon_get_fallback_icon_name (StIcon *icon)
+{
+ StIconPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ priv = icon->priv;
+
+ if (priv->fallback_gicon && G_IS_THEMED_ICON (priv->fallback_gicon))
+ return g_themed_icon_get_names (G_THEMED_ICON (priv->fallback_gicon)) [0];
+ else
+ return NULL;
+}
+
+/**
+ * st_icon_set_fallback_icon_name:
+ * @icon: an #StIcon
+ * @fallback_icon_name: (nullable): the name of the fallback icon
+ *
+ * This is a convenience method to set the fallback #GIcon to a #GThemedIcon
+ * created using the given icon name. If @fallback_icon_name is an empty
+ * string, %NULL or fails to load, the icon is unset and no texture will
+ * be visible for the fallback icon.
+ */
+void
+st_icon_set_fallback_icon_name (StIcon *icon,
+ const gchar *fallback_icon_name)
+{
+ g_autoptr(GIcon) gicon = NULL;
+
+ g_return_if_fail (ST_IS_ICON (icon));
+
+ if (g_strcmp0 (fallback_icon_name, st_icon_get_fallback_icon_name (icon)) == 0)
+ return;
+
+ if (fallback_icon_name && *fallback_icon_name)
+ gicon = g_themed_icon_new_with_default_fallbacks (fallback_icon_name);
+
+ g_object_freeze_notify (G_OBJECT (icon));
+
+ st_icon_set_fallback_gicon (icon, gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_FALLBACK_ICON_NAME]);
+
+ g_object_thaw_notify (G_OBJECT (icon));
+}
diff --git a/src/st/st-icon.h b/src/st/st-icon.h
new file mode 100644
index 0000000..8714ef9
--- /dev/null
+++ b/src/st/st-icon.h
@@ -0,0 +1,82 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-icon.h: icon widget
+ *
+ * Copyright 2009, 2010 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Written by: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef _ST_ICON
+#define _ST_ICON
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <st/st-widget.h>
+
+#include <st/st-types.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_ICON st_icon_get_type()
+G_DECLARE_FINAL_TYPE (StIcon, st_icon, ST, ICON, StWidget)
+
+typedef struct _StIconPrivate StIconPrivate;
+
+/**
+ * StIcon:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StIcon {
+ /*< private >*/
+ StWidget parent;
+
+ StIconPrivate *priv;
+};
+
+ClutterActor* st_icon_new (void);
+
+GIcon *st_icon_get_gicon (StIcon *icon);
+void st_icon_set_gicon (StIcon *icon,
+ GIcon *gicon);
+
+GIcon *st_icon_get_fallback_gicon (StIcon *icon);
+void st_icon_set_fallback_gicon (StIcon *icon,
+ GIcon *fallback_gicon);
+
+const gchar *st_icon_get_icon_name (StIcon *icon);
+void st_icon_set_icon_name (StIcon *icon,
+ const gchar *icon_name);
+
+const gchar *st_icon_get_fallback_icon_name (StIcon *icon);
+void st_icon_set_fallback_icon_name (StIcon *icon,
+ const gchar *fallback_icon_name);
+
+gint st_icon_get_icon_size (StIcon *icon);
+void st_icon_set_icon_size (StIcon *icon,
+ gint size);
+
+G_END_DECLS
+
+#endif /* _ST_ICON */
+
diff --git a/src/st/st-image-content.c b/src/st/st-image-content.c
new file mode 100644
index 0000000..92f1c14
--- /dev/null
+++ b/src/st/st-image-content.c
@@ -0,0 +1,346 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-image-content.h: A content image with scaling support
+ *
+ * Copyright 2019 Canonical, Ltd
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-image-content.h"
+#include "st-private.h"
+
+struct _StImageContent
+{
+ /*< private >*/
+ ClutterImage parent_instance;
+};
+
+typedef struct _StImageContentPrivate StImageContentPrivate;
+struct _StImageContentPrivate
+{
+ int width;
+ int height;
+};
+
+enum
+{
+ PROP_0,
+ PROP_PREFERRED_WIDTH,
+ PROP_PREFERRED_HEIGHT,
+};
+
+static void clutter_content_interface_init (ClutterContentInterface *iface);
+static void g_icon_interface_init (GIconIface *iface);
+static void g_loadable_icon_interface_init (GLoadableIconIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StImageContent, st_image_content, CLUTTER_TYPE_IMAGE,
+ G_ADD_PRIVATE (StImageContent)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
+ clutter_content_interface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ICON,
+ g_icon_interface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_LOADABLE_ICON,
+ g_loadable_icon_interface_init))
+
+static void
+st_image_content_init (StImageContent *self)
+{
+}
+
+static void
+st_image_content_constructed (GObject *object)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (object);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+
+ if (priv->width < 0 || priv->height < 0)
+ g_warning ("StImageContent initialized with invalid preferred size: %dx%d\n",
+ priv->width, priv->height);
+}
+
+static void
+st_image_content_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (object);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_PREFERRED_WIDTH:
+ g_value_set_int (value, priv->width);
+ break;
+
+ case PROP_PREFERRED_HEIGHT:
+ g_value_set_int (value, priv->height);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_image_content_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (object);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_PREFERRED_WIDTH:
+ priv->width = g_value_get_int (value);
+ break;
+
+ case PROP_PREFERRED_HEIGHT:
+ priv->height = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_image_content_class_init (StImageContentClass *klass)
+{
+ GParamSpec *pspec;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = st_image_content_constructed;
+ object_class->get_property = st_image_content_get_property;
+ object_class->set_property = st_image_content_set_property;
+
+ pspec = g_param_spec_int ("preferred-width",
+ "Preferred Width",
+ "Preferred Width of the Content when painted",
+ -1, G_MAXINT, -1,
+ G_PARAM_CONSTRUCT_ONLY | ST_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PREFERRED_WIDTH, pspec);
+
+ pspec = g_param_spec_int ("preferred-height",
+ "Preferred Height",
+ "Preferred Height of the Content when painted",
+ -1, G_MAXINT, -1,
+ G_PARAM_CONSTRUCT_ONLY | ST_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PREFERRED_HEIGHT, pspec);
+}
+
+static gboolean
+st_image_content_get_preferred_size (ClutterContent *content,
+ float *width,
+ float *height)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (content);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+ CoglTexture *texture;
+
+ texture = clutter_image_get_texture (CLUTTER_IMAGE (content));
+
+ if (texture == NULL)
+ return FALSE;
+
+ g_assert_cmpint (priv->width, >, -1);
+ g_assert_cmpint (priv->height, >, -1);
+
+ if (width != NULL)
+ *width = (float) priv->width;
+
+ if (height != NULL)
+ *height = (float) priv->height;
+
+ return TRUE;
+}
+
+static GdkPixbuf*
+pixbuf_from_image (StImageContent *image)
+{
+ CoglTexture *texture;
+ int width, height, rowstride;
+ uint8_t *data;
+
+ texture = clutter_image_get_texture (CLUTTER_IMAGE (image));
+ if (!texture || !cogl_texture_is_get_data_supported (texture))
+ return NULL;
+
+ width = cogl_texture_get_width (texture);
+ height = cogl_texture_get_width (texture);
+ rowstride = 4 * width;
+ data = g_new (uint8_t, rowstride * height);
+
+ cogl_texture_get_data (texture, COGL_PIXEL_FORMAT_RGBA_8888, rowstride, data);
+
+ return gdk_pixbuf_new_from_data ((const guchar *)data,
+ GDK_COLORSPACE_RGB,
+ TRUE, 8, width, height, rowstride,
+ (GdkPixbufDestroyNotify)g_free, NULL);
+}
+
+static void
+clutter_content_interface_init (ClutterContentInterface *iface)
+{
+ iface->get_preferred_size = st_image_content_get_preferred_size;
+}
+
+static guint
+st_image_content_hash (GIcon *icon)
+{
+ return g_direct_hash (icon);
+}
+
+static gboolean
+st_image_content_equal (GIcon *icon1,
+ GIcon *icon2)
+{
+ return g_direct_equal (icon1, icon2);
+}
+
+static GVariant *
+st_image_content_serialize (GIcon *icon)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+
+ pixbuf = pixbuf_from_image (ST_IMAGE_CONTENT (icon));
+ if (!pixbuf)
+ return NULL;
+
+ return g_icon_serialize (G_ICON (pixbuf));
+}
+
+static void
+g_icon_interface_init (GIconIface *iface)
+{
+ iface->hash = st_image_content_hash;
+ iface->equal = st_image_content_equal;
+ iface->serialize = st_image_content_serialize;
+}
+
+static GInputStream *
+st_image_load (GLoadableIcon *icon,
+ int size,
+ char **type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+
+ pixbuf = pixbuf_from_image (ST_IMAGE_CONTENT (icon));
+ if (!pixbuf)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed to read texture");
+ return NULL;
+ }
+
+ return g_loadable_icon_load (G_LOADABLE_ICON (pixbuf),
+ size, type, cancellable, error);
+}
+
+static void
+load_image_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GInputStream *stream;
+ GError *error = NULL;
+ char *type;
+
+ stream = st_image_load (G_LOADABLE_ICON (object),
+ GPOINTER_TO_INT (task_data),
+ &type,
+ cancellable,
+ &error);
+
+ if (error)
+ {
+ g_task_return_error (task, error);
+ }
+ else
+ {
+ g_task_set_task_data (task, type, g_free);
+ g_task_return_pointer (task, stream, g_object_unref);
+ }
+}
+
+static void
+st_image_load_async (GLoadableIcon *icon,
+ int size,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+
+ task = g_task_new (icon, cancellable, callback, user_data);
+ g_task_set_task_data (task, GINT_TO_POINTER (size), NULL);
+ g_task_run_in_thread (task, load_image_thread);
+}
+
+static GInputStream *
+st_image_load_finish (GLoadableIcon *icon,
+ GAsyncResult *res,
+ char **type,
+ GError **error)
+{
+ GInputStream *stream;
+
+ stream = g_task_propagate_pointer (G_TASK (res), error);
+ if (!stream)
+ return NULL;
+
+ if (type)
+ *type = g_strdup (g_task_get_task_data (G_TASK (res)));
+
+ return stream;
+}
+
+static void
+g_loadable_icon_interface_init (GLoadableIconIface *iface)
+{
+ iface->load = st_image_load;
+ iface->load_async = st_image_load_async;
+ iface->load_finish = st_image_load_finish;
+}
+
+/**
+ * st_image_content_new_with_preferred_size:
+ * @width: The preferred width to be used when drawing the content
+ * @height: The preferred width to be used when drawing the content
+ *
+ * Creates a new #StImageContent, a simple content for sized images.
+ *
+ * See #ClutterImage for setting the actual image to display or #StIcon for
+ * displaying icons.
+ *
+ * Returns: (transfer full): the newly created #StImageContent content
+ * Use g_object_unref() when done.
+ */
+ClutterContent *
+st_image_content_new_with_preferred_size (int width,
+ int height)
+{
+ return g_object_new (ST_TYPE_IMAGE_CONTENT,
+ "preferred-width", width,
+ "preferred-height", height,
+ NULL);
+}
diff --git a/src/st/st-image-content.h b/src/st/st-image-content.h
new file mode 100644
index 0000000..0ebb0b7
--- /dev/null
+++ b/src/st/st-image-content.h
@@ -0,0 +1,33 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-image-content.h: A content image with scaling support
+ *
+ * Copyright 2019 Canonical, Ltd
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_IMAGE_CONTENT_H__
+#define __ST_IMAGE_CONTENT_H__
+
+#include <clutter/clutter.h>
+
+#define ST_TYPE_IMAGE_CONTENT (st_image_content_get_type ())
+G_DECLARE_FINAL_TYPE (StImageContent, st_image_content,
+ ST, IMAGE_CONTENT, ClutterImage)
+
+ClutterContent *st_image_content_new_with_preferred_size (int width,
+ int height);
+
+#endif /* __ST_IMAGE_CONTENT_H__ */
diff --git a/src/st/st-label.c b/src/st/st-label.c
new file mode 100644
index 0000000..fe77743
--- /dev/null
+++ b/src/st/st-label.c
@@ -0,0 +1,549 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-label.c: Plain label actor
+ *
+ * Copyright 2008,2009 Intel Corporation
+ * Copyright 2009 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-label
+ * @short_description: Widget for displaying text
+ *
+ * #StLabel is a simple widget for displaying text. It derives from
+ * #StWidget to add extra style and placement functionality over
+ * #ClutterText. The internal #ClutterText is publicly accessibly to allow
+ * applications to set further properties.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+#include "st-label.h"
+#include "st-private.h"
+#include "st-widget.h"
+
+#include <st/st-widget-accessible.h>
+
+enum
+{
+ PROP_0,
+
+ PROP_CLUTTER_TEXT,
+ PROP_TEXT,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+struct _StLabelPrivate
+{
+ ClutterActor *label;
+
+ StShadow *shadow_spec;
+
+ CoglPipeline *text_shadow_pipeline;
+ float shadow_width;
+ float shadow_height;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StLabel, st_label, ST_TYPE_WIDGET);
+
+static GType st_label_accessible_get_type (void) G_GNUC_CONST;
+
+static void
+st_label_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StLabel *label = ST_LABEL (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_TEXT:
+ st_label_set_text (label, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_label_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StLabelPrivate *priv = ST_LABEL (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_CLUTTER_TEXT:
+ g_value_set_object (value, priv->label);
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->label)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_label_style_changed (StWidget *self)
+{
+ StLabelPrivate *priv = ST_LABEL(self)->priv;
+ StThemeNode *theme_node;
+ StShadow *shadow_spec;
+
+ theme_node = st_widget_get_theme_node (self);
+
+ shadow_spec = st_theme_node_get_text_shadow (theme_node);
+ if (!priv->shadow_spec || !shadow_spec ||
+ !st_shadow_equal (shadow_spec, priv->shadow_spec))
+ {
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+ if (shadow_spec)
+ priv->shadow_spec = st_shadow_ref (shadow_spec);
+ }
+
+ _st_set_text_from_style ((ClutterText *)priv->label, st_widget_get_theme_node (self));
+
+ ST_WIDGET_CLASS (st_label_parent_class)->style_changed (self);
+}
+
+static void
+st_label_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ clutter_actor_get_preferred_width (priv->label, for_height,
+ min_width_p,
+ natural_width_p);
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_label_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ clutter_actor_get_preferred_height (priv->label, for_width,
+ min_height_p,
+ natural_height_p);
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_label_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box;
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ clutter_actor_allocate (priv->label, &content_box);
+}
+
+static void
+st_label_dispose (GObject *object)
+{
+ StLabelPrivate *priv = ST_LABEL (object)->priv;
+
+ priv->label = NULL;
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ G_OBJECT_CLASS (st_label_parent_class)->dispose (object);
+}
+
+static void
+st_label_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->shadow_spec)
+ {
+ ClutterActorBox allocation;
+ float width, height;
+ float resource_scale;
+
+ clutter_actor_get_allocation_box (priv->label, &allocation);
+ clutter_actor_box_get_size (&allocation, &width, &height);
+
+ resource_scale = clutter_actor_get_resource_scale (priv->label);
+
+ width *= resource_scale;
+ height *= resource_scale;
+
+ if (priv->text_shadow_pipeline == NULL ||
+ width != priv->shadow_width ||
+ height != priv->shadow_height)
+ {
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ priv->shadow_width = width;
+ priv->shadow_height = height;
+ priv->text_shadow_pipeline =
+ _st_create_shadow_pipeline_from_actor (priv->shadow_spec,
+ priv->label);
+ }
+
+ if (priv->text_shadow_pipeline != NULL)
+ {
+ CoglFramebuffer *framebuffer;
+
+ framebuffer =
+ clutter_paint_context_get_framebuffer (paint_context);
+ _st_paint_shadow_with_opacity (priv->shadow_spec,
+ framebuffer,
+ priv->text_shadow_pipeline,
+ &allocation,
+ clutter_actor_get_paint_opacity (priv->label));
+ }
+ }
+
+ clutter_actor_paint (priv->label, paint_context);
+}
+
+static void
+st_label_resource_scale_changed (ClutterActor *actor)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ if (CLUTTER_ACTOR_CLASS (st_label_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_label_parent_class)->resource_scale_changed (actor);
+}
+
+static void
+st_label_class_init (StLabelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_label_set_property;
+ gobject_class->get_property = st_label_get_property;
+ gobject_class->dispose = st_label_dispose;
+
+ actor_class->paint = st_label_paint;
+ actor_class->allocate = st_label_allocate;
+ actor_class->get_preferred_width = st_label_get_preferred_width;
+ actor_class->get_preferred_height = st_label_get_preferred_height;
+ actor_class->resource_scale_changed = st_label_resource_scale_changed;
+
+ widget_class->style_changed = st_label_style_changed;
+ widget_class->get_accessible_type = st_label_accessible_get_type;
+
+ /**
+ * StLabel:clutter-text:
+ *
+ * The internal #ClutterText actor supporting the label
+ */
+ props[PROP_CLUTTER_TEXT] =
+ g_param_spec_object ("clutter-text",
+ "Clutter Text",
+ "Internal ClutterText actor",
+ CLUTTER_TYPE_TEXT,
+ ST_PARAM_READABLE);
+
+ /**
+ * StLabel:text:
+ *
+ * The current text being display in the #StLabel.
+ */
+ props[PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "Text of the label",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+invalidate_shadow_pipeline (GObject *object,
+ GParamSpec *pspec,
+ StLabel *label)
+{
+ StLabelPrivate *priv = st_label_get_instance_private (label);
+
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+}
+
+static void
+st_label_init (StLabel *label)
+{
+ ClutterActor *actor = CLUTTER_ACTOR (label);
+ StLabelPrivate *priv;
+
+ label->priv = priv = st_label_get_instance_private (label);
+
+ label->priv->label = g_object_new (CLUTTER_TYPE_TEXT,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+ label->priv->text_shadow_pipeline = NULL;
+ label->priv->shadow_width = -1.;
+ label->priv->shadow_height = -1.;
+
+ /* These properties might get set from CSS using _st_set_text_from_style */
+ g_signal_connect (priv->label, "notify::font-description",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ g_signal_connect (priv->label, "notify::attributes",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ g_signal_connect (priv->label, "notify::justify",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ g_signal_connect (priv->label, "notify::line-alignment",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ clutter_actor_add_child (actor, priv->label);
+
+ clutter_actor_set_offscreen_redirect (actor,
+ CLUTTER_OFFSCREEN_REDIRECT_ALWAYS);
+}
+
+/**
+ * st_label_new:
+ * @text: (nullable): text to set the label to
+ *
+ * Create a new #StLabel with the label specified by @text.
+ *
+ * Returns: a new #StLabel
+ */
+StWidget *
+st_label_new (const gchar *text)
+{
+ if (text == NULL || *text == '\0')
+ return g_object_new (ST_TYPE_LABEL, NULL);
+ else
+ return g_object_new (ST_TYPE_LABEL,
+ "text", text,
+ NULL);
+}
+
+/**
+ * st_label_get_text:
+ * @label: a #StLabel
+ *
+ * Get the text displayed on the label.
+ *
+ * Returns: (transfer none): the text for the label. This must not be freed by
+ * the application
+ */
+const gchar *
+st_label_get_text (StLabel *label)
+{
+ g_return_val_if_fail (ST_IS_LABEL (label), NULL);
+
+ return clutter_text_get_text (CLUTTER_TEXT (label->priv->label));
+}
+
+/**
+ * st_label_set_text:
+ * @label: a #StLabel
+ * @text: (nullable): text to set the label to
+ *
+ * Sets the text displayed by the label.
+ */
+void
+st_label_set_text (StLabel *label,
+ const gchar *text)
+{
+ StLabelPrivate *priv;
+ ClutterText *ctext;
+
+ g_return_if_fail (ST_IS_LABEL (label));
+
+ priv = label->priv;
+ ctext = CLUTTER_TEXT (priv->label);
+
+ if (clutter_text_get_editable (ctext) ||
+ g_strcmp0 (clutter_text_get_text (ctext), text) != 0)
+ {
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ clutter_text_set_text (ctext, text);
+
+ g_object_notify_by_pspec (G_OBJECT (label), props[PROP_TEXT]);
+ }
+}
+
+/**
+ * st_label_get_clutter_text:
+ * @label: a #StLabel
+ *
+ * Retrieve the internal #ClutterText used by @label so that extra parameters
+ * can be set.
+ *
+ * Returns: (transfer none): the #ClutterText used by #StLabel. The actor
+ * is owned by the #StLabel and should not be destroyed by the application.
+ */
+ClutterActor*
+st_label_get_clutter_text (StLabel *label)
+{
+ g_return_val_if_fail (ST_LABEL (label), NULL);
+
+ return label->priv->label;
+}
+
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+#define ST_TYPE_LABEL_ACCESSIBLE st_label_accessible_get_type ()
+
+#define ST_LABEL_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessible))
+
+#define ST_IS_LABEL_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ ST_TYPE_LABEL_ACCESSIBLE))
+
+#define ST_LABEL_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessibleClass))
+
+#define ST_IS_LABEL_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ ST_TYPE_LABEL_ACCESSIBLE))
+
+#define ST_LABEL_ACCESSIBLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessibleClass))
+
+typedef struct _StLabelAccessible StLabelAccessible;
+typedef struct _StLabelAccessibleClass StLabelAccessibleClass;
+
+struct _StLabelAccessible
+{
+ StWidgetAccessible parent;
+};
+
+struct _StLabelAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+/* AtkObject */
+static void st_label_accessible_initialize (AtkObject *obj,
+ gpointer data);
+static const gchar * st_label_accessible_get_name (AtkObject *obj);
+
+G_DEFINE_TYPE (StLabelAccessible, st_label_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
+
+static void
+st_label_accessible_class_init (StLabelAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ atk_class->initialize = st_label_accessible_initialize;
+ atk_class->get_name = st_label_accessible_get_name;
+}
+
+static void
+st_label_accessible_init (StLabelAccessible *self)
+{
+ /* initialization done on AtkObject->initialize */
+}
+
+static void
+label_text_notify_cb (StLabel *label,
+ GParamSpec *pspec,
+ AtkObject *accessible)
+{
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+}
+
+static void
+st_label_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_label_accessible_parent_class)->initialize (obj, data);
+
+ g_signal_connect (data, "notify::text",
+ G_CALLBACK (label_text_notify_cb),
+ obj);
+
+ obj->role = ATK_ROLE_LABEL;
+}
+
+static const gchar *
+st_label_accessible_get_name (AtkObject *obj)
+{
+ const gchar *name = NULL;
+
+ g_return_val_if_fail (ST_IS_LABEL_ACCESSIBLE (obj), NULL);
+
+ name = ATK_OBJECT_CLASS (st_label_accessible_parent_class)->get_name (obj);
+ if (name == NULL)
+ {
+ ClutterActor *actor = NULL;
+
+ actor = CLUTTER_ACTOR (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (actor == NULL || st_widget_has_style_class_name (ST_WIDGET (actor), "hidden"))
+ name = NULL;
+ else
+ name = st_label_get_text (ST_LABEL (actor));
+ }
+
+ return name;
+}
diff --git a/src/st/st-label.h b/src/st/st-label.h
new file mode 100644
index 0000000..456ad31
--- /dev/null
+++ b/src/st/st-label.h
@@ -0,0 +1,58 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-label.h: Plain label actor
+ *
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_LABEL_H__
+#define __ST_LABEL_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-widget.h>
+
+#define ST_TYPE_LABEL (st_label_get_type ())
+G_DECLARE_FINAL_TYPE (StLabel, st_label, ST, LABEL, StWidget)
+
+typedef struct _StLabelPrivate StLabelPrivate;
+
+/**
+ * StLabel:
+ *
+ * The contents of this structure is private and should only be accessed using
+ * the provided API.
+ */
+struct _StLabel
+{
+ /*< private >*/
+ StWidget parent_instance;
+
+ StLabelPrivate *priv;
+};
+
+StWidget * st_label_new (const gchar *text);
+const gchar * st_label_get_text (StLabel *label);
+void st_label_set_text (StLabel *label,
+ const gchar *text);
+ClutterActor * st_label_get_clutter_text (StLabel *label);
+
+G_END_DECLS
+
+#endif /* __ST_LABEL_H__ */
diff --git a/src/st/st-password-entry.c b/src/st/st-password-entry.c
new file mode 100644
index 0000000..43f9b52
--- /dev/null
+++ b/src/st/st-password-entry.c
@@ -0,0 +1,363 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-password-entry.c: Password entry actor based on st-entry
+ *
+ * Copyright 2019 Endless Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-private.h"
+#include "st-password-entry.h"
+#include "st-icon.h"
+#include "st-settings.h"
+
+#define BLACK_CIRCLE 9679
+
+#define ST_PASSWORD_ENTRY_PRIV(x) st_password_entry_get_instance_private ((StPasswordEntry *) x)
+
+typedef struct _StPasswordEntryPrivate StPasswordEntryPrivate;
+
+struct _StPasswordEntry
+{
+ /*< private >*/
+ StEntry parent_instance;
+};
+
+struct _StPasswordEntryPrivate
+{
+ ClutterActor *peek_password_icon;
+
+ gboolean password_visible;
+ gboolean show_peek_icon;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_PASSWORD_VISIBLE,
+ PROP_SHOW_PEEK_ICON,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StPasswordEntry, st_password_entry, ST_TYPE_ENTRY);
+
+static gboolean
+show_password_locked_down (StPasswordEntry *entry)
+{
+ gboolean disable_show_password = FALSE;
+
+ g_object_get (st_settings_get (), "disable-show-password", &disable_show_password, NULL);
+
+ return disable_show_password;
+}
+
+static void
+st_password_entry_secondary_icon_clicked (StEntry *entry)
+{
+ StPasswordEntry *password_entry = ST_PASSWORD_ENTRY (entry);
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (password_entry);
+
+ st_password_entry_set_password_visible (password_entry, !priv->password_visible);
+}
+
+static void
+st_password_entry_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (gobject);
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PASSWORD_VISIBLE:
+ g_value_set_boolean (value, priv->password_visible);
+ break;
+
+ case PROP_SHOW_PEEK_ICON:
+ g_value_set_boolean (value, st_password_entry_get_show_peek_icon (entry));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_password_entry_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PASSWORD_VISIBLE:
+ st_password_entry_set_password_visible (entry, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_PEEK_ICON:
+ st_password_entry_set_show_peek_icon (entry, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_password_entry_dispose (GObject *gobject)
+{
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (gobject);
+
+ g_clear_object (&priv->peek_password_icon);
+
+ G_OBJECT_CLASS(st_password_entry_parent_class)->dispose (gobject);
+}
+
+static void
+st_password_entry_class_init (StPasswordEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ StEntryClass *st_entry_class = ST_ENTRY_CLASS (klass);
+
+ gobject_class->get_property = st_password_entry_get_property;
+ gobject_class->set_property = st_password_entry_set_property;
+ gobject_class->dispose = st_password_entry_dispose;
+
+ st_entry_class->secondary_icon_clicked = st_password_entry_secondary_icon_clicked;
+
+ /**
+ * StPasswordEntry:password-visible:
+ *
+ * Whether the text in the entry is masked for privacy.
+ */
+ props[PROP_PASSWORD_VISIBLE] = g_param_spec_boolean ("password-visible",
+ "Password visible",
+ "Whether the text in the entry is masked or not",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StPasswordEntry:show-peek-icon:
+ *
+ * Whether to display an icon button to toggle the masking enabled by the
+ * #StPasswordEntry:password-visible property.
+ */
+ props[PROP_SHOW_PEEK_ICON] = g_param_spec_boolean ("show-peek-icon",
+ "Show peek icon",
+ "Whether to show the password peek icon",
+ TRUE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+update_peek_icon (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ gboolean show_peek_icon;
+
+ show_peek_icon = st_password_entry_get_show_peek_icon (entry);
+
+ if (show_peek_icon)
+ st_entry_set_secondary_icon (ST_ENTRY (entry), priv->peek_password_icon);
+ else
+ st_entry_set_secondary_icon (ST_ENTRY (entry), NULL);
+}
+
+static void
+on_disable_show_password_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (user_data);
+
+ if (show_password_locked_down (entry))
+ st_password_entry_set_password_visible (entry, FALSE);
+
+ update_peek_icon (entry);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SHOW_PEEK_ICON]);
+}
+
+static void
+clutter_text_password_char_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (user_data);
+ ClutterActor *clutter_text;
+
+ clutter_text = st_entry_get_clutter_text (ST_ENTRY (entry));
+ if (clutter_text_get_password_char (CLUTTER_TEXT (clutter_text)) == 0)
+ st_password_entry_set_password_visible (entry, TRUE);
+ else
+ st_password_entry_set_password_visible (entry, FALSE);
+}
+
+static void
+st_password_entry_init (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ ClutterActor *clutter_text;
+
+ priv->peek_password_icon = g_object_new (ST_TYPE_ICON,
+ "style-class", "peek-password",
+ "icon-name", "view-reveal-symbolic",
+ NULL);
+ st_entry_set_secondary_icon (ST_ENTRY (entry), priv->peek_password_icon);
+
+ st_password_entry_set_show_peek_icon (entry, TRUE);
+
+ g_signal_connect_object (st_settings_get (),
+ "notify::disable-show-password",
+ G_CALLBACK (on_disable_show_password_changed),
+ entry,
+ 0);
+
+ clutter_text = st_entry_get_clutter_text (ST_ENTRY (entry));
+ clutter_text_set_password_char (CLUTTER_TEXT (clutter_text), BLACK_CIRCLE);
+
+ st_entry_set_input_purpose (ST_ENTRY (entry), CLUTTER_INPUT_CONTENT_PURPOSE_PASSWORD);
+
+ g_signal_connect (clutter_text, "notify::password-char",
+ G_CALLBACK (clutter_text_password_char_cb), entry);
+}
+
+/**
+ * st_password_entry_new:
+ *
+ * Create a new #StPasswordEntry.
+ *
+ * Returns: a new #StEntry
+ */
+StEntry*
+st_password_entry_new (void)
+{
+ return ST_ENTRY (g_object_new (ST_TYPE_PASSWORD_ENTRY, NULL));
+}
+
+/**
+ * st_password_entry_set_show_peek_icon:
+ * @entry: a #StPasswordEntry
+ * @value: %TRUE to show the peek-icon in the entry
+ *
+ * Sets whether to show or hide the peek-icon in the password entry. If %TRUE,
+ * a icon button for temporarily unmasking the password will be shown at the
+ * end of the entry.
+ */
+void
+st_password_entry_set_show_peek_icon (StPasswordEntry *entry,
+ gboolean value)
+{
+ StPasswordEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_PASSWORD_ENTRY (entry));
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ if (priv->show_peek_icon == value)
+ return;
+
+ priv->show_peek_icon = value;
+
+ update_peek_icon (entry);
+
+ if (st_password_entry_get_show_peek_icon (entry) != value)
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SHOW_PEEK_ICON]);
+}
+
+/**
+ * st_password_entry_get_show_peek_icon:
+ * @entry: a #StPasswordEntry
+ *
+ * Gets whether peek-icon is shown or hidden in the password entry.
+ *
+ * Returns: %TRUE if visible
+ */
+gboolean
+st_password_entry_get_show_peek_icon (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_PASSWORD_ENTRY (entry), TRUE);
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ return priv->show_peek_icon && !show_password_locked_down (entry);
+}
+
+/**
+ * st_password_entry_set_password_visible:
+ * @entry: a #StPasswordEntry
+ * @value: %TRUE to show the password in the entry, #FALSE otherwise
+ *
+ * Sets whether to show or hide text in the password entry.
+ */
+void
+st_password_entry_set_password_visible (StPasswordEntry *entry,
+ gboolean value)
+{
+ StPasswordEntryPrivate *priv;
+ ClutterActor *clutter_text;
+
+ g_return_if_fail (ST_IS_PASSWORD_ENTRY (entry));
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ if (priv->password_visible == value)
+ return;
+
+ priv->password_visible = value;
+
+ clutter_text = st_entry_get_clutter_text (ST_ENTRY (entry));
+ if (priv->password_visible)
+ {
+ clutter_text_set_password_char (CLUTTER_TEXT (clutter_text), 0);
+ st_icon_set_icon_name (ST_ICON (priv->peek_password_icon), "view-conceal-symbolic");
+ }
+ else
+ {
+ clutter_text_set_password_char (CLUTTER_TEXT (clutter_text), BLACK_CIRCLE);
+ st_icon_set_icon_name (ST_ICON (priv->peek_password_icon), "view-reveal-symbolic");
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PASSWORD_VISIBLE]);
+}
+
+/**
+ * st_password_entry_get_password_visible:
+ * @entry: a #StPasswordEntry
+ *
+ * Gets whether the text is masked in the password entry.
+ *
+ * Returns: %TRUE if visible
+ */
+gboolean
+st_password_entry_get_password_visible (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_PASSWORD_ENTRY (entry), FALSE);
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ return priv->password_visible;
+}
diff --git a/src/st/st-password-entry.h b/src/st/st-password-entry.h
new file mode 100644
index 0000000..3998068
--- /dev/null
+++ b/src/st/st-password-entry.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-password-entry.h: Password entry actor based on st-entry
+ *
+ * Copyright 2019 Endless Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_PASSWORD_ENTRY_H__
+#define __ST_PASSWORD_ENTRY_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-entry.h>
+
+#define ST_TYPE_PASSWORD_ENTRY (st_password_entry_get_type ())
+
+G_DECLARE_FINAL_TYPE (StPasswordEntry, st_password_entry, ST, PASSWORD_ENTRY, StEntry)
+
+StEntry *st_password_entry_new (void);
+gboolean st_password_entry_get_password_visible (StPasswordEntry *entry);
+void st_password_entry_set_password_visible (StPasswordEntry *entry,
+ gboolean value);
+gboolean st_password_entry_get_show_peek_icon (StPasswordEntry *entry);
+void st_password_entry_set_show_peek_icon (StPasswordEntry *entry,
+ gboolean value);
+
+G_END_DECLS
+
+#endif /* __ST_PASSWORD_ENTRY_H__ */
+
diff --git a/src/st/st-private.c b/src/st/st-private.c
new file mode 100644
index 0000000..bb98151
--- /dev/null
+++ b/src/st/st-private.c
@@ -0,0 +1,804 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-private.h: Private declarations and functions
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ * Copyright 2010 Intel Corporation
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <math.h>
+#include <string.h>
+
+#include "st-private.h"
+
+/**
+ * _st_actor_get_preferred_width:
+ * @actor: a #ClutterActor
+ * @for_height: as with clutter_actor_get_preferred_width()
+ * @y_fill: %TRUE if @actor will fill its allocation vertically
+ * @min_width_p: as with clutter_actor_get_preferred_width()
+ * @natural_width_p: as with clutter_actor_get_preferred_width()
+ *
+ * Like clutter_actor_get_preferred_width(), but if @y_fill is %FALSE,
+ * then it will compute a width request based on the assumption that
+ * @actor will be given an allocation no taller than its natural
+ * height.
+ */
+void
+_st_actor_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gboolean y_fill,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ if (!y_fill && for_height != -1)
+ {
+ ClutterRequestMode mode;
+ gfloat natural_height;
+
+ mode = clutter_actor_get_request_mode (actor);
+ if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT)
+ {
+ clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height);
+ if (for_height > natural_height)
+ for_height = natural_height;
+ }
+ }
+
+ clutter_actor_get_preferred_width (actor, for_height, min_width_p, natural_width_p);
+}
+
+/**
+ * _st_actor_get_preferred_height:
+ * @actor: a #ClutterActor
+ * @for_width: as with clutter_actor_get_preferred_height()
+ * @x_fill: %TRUE if @actor will fill its allocation horizontally
+ * @min_height_p: as with clutter_actor_get_preferred_height()
+ * @natural_height_p: as with clutter_actor_get_preferred_height()
+ *
+ * Like clutter_actor_get_preferred_height(), but if @x_fill is
+ * %FALSE, then it will compute a height request based on the
+ * assumption that @actor will be given an allocation no wider than
+ * its natural width.
+ */
+void
+_st_actor_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gboolean x_fill,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ if (!x_fill && for_width != -1)
+ {
+ ClutterRequestMode mode;
+ gfloat natural_width;
+
+ mode = clutter_actor_get_request_mode (actor);
+ if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width);
+ if (for_width > natural_width)
+ for_width = natural_width;
+ }
+ }
+
+ clutter_actor_get_preferred_height (actor, for_width, min_height_p, natural_height_p);
+}
+
+/**
+ * _st_set_text_from_style:
+ * @text: Target #ClutterText
+ * @theme_node: Source #StThemeNode
+ *
+ * Set various GObject properties of the @text object using
+ * CSS information from @theme_node.
+ */
+void
+_st_set_text_from_style (ClutterText *text,
+ StThemeNode *theme_node)
+{
+
+ ClutterColor color;
+ StTextDecoration decoration;
+ PangoAttrList *attribs = NULL;
+ const PangoFontDescription *font;
+ PangoAttribute *foreground;
+ StTextAlign align;
+ gdouble spacing;
+ gchar *font_features;
+
+ font = st_theme_node_get_font (theme_node);
+ clutter_text_set_font_description (text, (PangoFontDescription *) font);
+
+ attribs = pango_attr_list_new ();
+
+ st_theme_node_get_foreground_color (theme_node, &color);
+ clutter_text_set_cursor_color (text, &color);
+ foreground = pango_attr_foreground_new (color.red * 255,
+ color.green * 255,
+ color.blue * 255);
+ pango_attr_list_insert (attribs, foreground);
+
+ if (color.alpha != 255)
+ {
+ PangoAttribute *alpha;
+
+ /* An alpha value of 0 means "system inherited", so the
+ * minimum regular value is 1.
+ */
+ if (color.alpha == 0)
+ alpha = pango_attr_foreground_alpha_new (1);
+ else
+ alpha = pango_attr_foreground_alpha_new (color.alpha * 255);
+
+ pango_attr_list_insert (attribs, alpha);
+ }
+
+ decoration = st_theme_node_get_text_decoration (theme_node);
+ if (decoration)
+ {
+ if (decoration & ST_TEXT_DECORATION_UNDERLINE)
+ {
+ PangoAttribute *underline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+ pango_attr_list_insert (attribs, underline);
+ }
+ if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
+ {
+ PangoAttribute *strikethrough = pango_attr_strikethrough_new (TRUE);
+ pango_attr_list_insert (attribs, strikethrough);
+ }
+ /* Pango doesn't have an equivalent attribute for _OVERLINE, and we deliberately
+ * skip BLINK (for now...)
+ */
+ }
+
+ spacing = st_theme_node_get_letter_spacing (theme_node);
+ if (spacing)
+ {
+ PangoAttribute *letter_spacing = pango_attr_letter_spacing_new ((int)(.5 + spacing) * PANGO_SCALE);
+ pango_attr_list_insert (attribs, letter_spacing);
+ }
+
+ font_features = st_theme_node_get_font_features (theme_node);
+ if (font_features)
+ {
+ pango_attr_list_insert (attribs, pango_attr_font_features_new (font_features));
+ g_free (font_features);
+ }
+
+ clutter_text_set_attributes (text, attribs);
+
+ if (attribs)
+ pango_attr_list_unref (attribs);
+
+ align = st_theme_node_get_text_align (theme_node);
+ if (align == ST_TEXT_ALIGN_JUSTIFY)
+ {
+ clutter_text_set_justify (text, TRUE);
+ clutter_text_set_line_alignment (text, PANGO_ALIGN_LEFT);
+ }
+ else
+ {
+ clutter_text_set_justify (text, FALSE);
+ clutter_text_set_line_alignment (text, (PangoAlignment) align);
+ }
+}
+
+/**
+ * _st_create_texture_pipeline:
+ * @src_texture: The CoglTexture for the pipeline
+ *
+ * Creates a simple pipeline which contains the given texture as a
+ * single layer.
+ */
+CoglPipeline *
+_st_create_texture_pipeline (CoglTexture *src_texture)
+{
+ static CoglPipeline *texture_pipeline_template = NULL;
+ CoglPipeline *pipeline;
+
+ g_return_val_if_fail (src_texture != NULL, NULL);
+
+ /* The only state used in the pipeline that would affect the shader
+ generation is the texture type on the layer. Therefore we create
+ a template pipeline which sets this state and all texture
+ pipelines are created as a copy of this. That way Cogl can find
+ the shader state for the pipeline more quickly by looking at the
+ pipeline ancestry instead of resorting to the shader cache. */
+ if (G_UNLIKELY (texture_pipeline_template == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ texture_pipeline_template = cogl_pipeline_new (ctx);
+ cogl_pipeline_set_layer_null_texture (texture_pipeline_template, 0);
+ }
+
+ pipeline = cogl_pipeline_copy (texture_pipeline_template);
+
+ if (src_texture != NULL)
+ cogl_pipeline_set_layer_texture (pipeline, 0, src_texture);
+
+ return pipeline;
+}
+
+/*****
+ * Shadows
+ *****/
+
+static gdouble *
+calculate_gaussian_kernel (gdouble sigma,
+ guint n_values)
+{
+ gdouble *ret, sum;
+ gdouble exp_divisor;
+ int half, i;
+
+ g_return_val_if_fail (sigma > 0, NULL);
+
+ half = n_values / 2;
+
+ ret = g_malloc (n_values * sizeof (gdouble));
+ sum = 0.0;
+
+ exp_divisor = 2 * sigma * sigma;
+
+ /* n_values of 1D Gauss function */
+ for (i = 0; i < (int)n_values; i++)
+ {
+ ret[i] = exp (-(i - half) * (i - half) / exp_divisor);
+ sum += ret[i];
+ }
+
+ /* normalize */
+ for (i = 0; i < (int)n_values; i++)
+ ret[i] /= sum;
+
+ return ret;
+}
+
+static guchar *
+blur_pixels (guchar *pixels_in,
+ gint width_in,
+ gint height_in,
+ gint rowstride_in,
+ gdouble blur,
+ gint *width_out,
+ gint *height_out,
+ size_t *rowstride_out)
+{
+ guchar *pixels_out;
+ gdouble sigma;
+
+ /* The CSS specification defines (or will define) the blur radius as twice
+ * the Gaussian standard deviation. See:
+ *
+ * http://lists.w3.org/Archives/Public/www-style/2010Sep/0002.html
+ */
+ sigma = blur / 2.;
+
+ if ((guint) blur == 0)
+ {
+ *width_out = width_in;
+ *height_out = height_in;
+ *rowstride_out = rowstride_in;
+ pixels_out = g_memdup2 (pixels_in, *rowstride_out * *height_out);
+ }
+ else
+ {
+ gdouble *kernel;
+ guchar *line;
+ gint n_values, half;
+ gint x_in, y_in, x_out, y_out, i;
+
+ n_values = (gint) 5 * sigma;
+ half = n_values / 2;
+
+ *width_out = width_in + 2 * half;
+ *height_out = height_in + 2 * half;
+ *rowstride_out = (*width_out + 3) & ~3;
+
+ pixels_out = g_malloc0 (*rowstride_out * *height_out);
+ line = g_malloc0 (*rowstride_out);
+
+ kernel = calculate_gaussian_kernel (sigma, n_values);
+
+ /* vertical blur */
+ for (x_in = 0; x_in < width_in; x_in++)
+ for (y_out = 0; y_out < *height_out; y_out++)
+ {
+ guchar *pixel_in, *pixel_out;
+ gint i0, i1;
+
+ y_in = y_out - half;
+
+ /* We read from the source at 'y = y_in + i - half'; clamp the
+ * full i range [0, n_values) so that y is in [0, height_in).
+ */
+ i0 = MAX (half - y_in, 0);
+ i1 = MIN (height_in + half - y_in, n_values);
+
+ pixel_in = pixels_in + (y_in + i0 - half) * rowstride_in + x_in;
+ pixel_out = pixels_out + y_out * *rowstride_out + (x_in + half);
+
+ for (i = i0; i < i1; i++)
+ {
+ *pixel_out += *pixel_in * kernel[i];
+ pixel_in += rowstride_in;
+ }
+ }
+
+ /* horizontal blur */
+ for (y_out = 0; y_out < *height_out; y_out++)
+ {
+ memcpy (line, pixels_out + y_out * *rowstride_out, *rowstride_out);
+
+ for (x_out = 0; x_out < *width_out; x_out++)
+ {
+ gint i0, i1;
+ guchar *pixel_out, *pixel_in;
+
+ /* We read from the source at 'x = x_out + i - half'; clamp the
+ * full i range [0, n_values) so that x is in [0, width_out).
+ */
+ i0 = MAX (half - x_out, 0);
+ i1 = MIN (*width_out + half - x_out, n_values);
+
+ pixel_in = line + x_out + i0 - half;
+ pixel_out = pixels_out + *rowstride_out * y_out + x_out;
+
+ *pixel_out = 0;
+ for (i = i0; i < i1; i++)
+ {
+ *pixel_out += *pixel_in * kernel[i];
+ pixel_in++;
+ }
+ }
+ }
+ g_free (kernel);
+ g_free (line);
+ }
+
+ return pixels_out;
+}
+
+CoglPipeline *
+_st_create_shadow_pipeline (StShadow *shadow_spec,
+ CoglTexture *src_texture,
+ float resource_scale)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ g_autoptr (ClutterPaintNode) texture_node = NULL;
+ g_autoptr (ClutterPaintNode) blur_node = NULL;
+ g_autoptr (CoglOffscreen) offscreen = NULL;
+ g_autoptr (GError) error = NULL;
+ ClutterPaintContext *paint_context;
+ CoglFramebuffer *fb;
+ CoglPipeline *pipeline;
+ CoglTexture *texture;
+ float sampling_radius;
+ float sigma;
+ int src_height, dst_height;
+ int src_width, dst_width;
+ CoglPipeline *texture_pipeline;
+
+ static CoglPipelineKey texture_pipeline_key =
+ "st-create-shadow-pipeline-saturate-alpha";
+ static CoglPipeline *shadow_pipeline_template = NULL;
+
+ g_return_val_if_fail (shadow_spec != NULL, NULL);
+ g_return_val_if_fail (src_texture != NULL, NULL);
+
+ sampling_radius = resource_scale * shadow_spec->blur;
+ sigma = sampling_radius / 2.f;
+ sampling_radius = ceilf (sampling_radius);
+
+ src_width = cogl_texture_get_width (src_texture);
+ src_height = cogl_texture_get_height (src_texture);
+ dst_width = src_width + 2 * sampling_radius;
+ dst_height = src_height + 2 * sampling_radius;
+
+ texture = cogl_texture_2d_new_with_size (ctx, dst_width, dst_height);
+ if (!texture)
+ return NULL;
+
+ offscreen = cogl_offscreen_new_with_texture (texture);
+ fb = COGL_FRAMEBUFFER (offscreen);
+ if (!cogl_framebuffer_allocate (fb, &error))
+ {
+ cogl_clear_object (&texture);
+ return NULL;
+ }
+
+ cogl_framebuffer_clear4f (fb, COGL_BUFFER_BIT_COLOR, 0.f, 0.f, 0.f, 0.f);
+ cogl_framebuffer_orthographic (fb, 0, 0, dst_width, dst_height, 0, 1.0);
+
+ /* Blur */
+ blur_node = clutter_blur_node_new (dst_width, dst_height, sigma);
+ clutter_paint_node_add_rectangle (blur_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ dst_width, dst_height,
+ });
+
+ /* Texture */
+ texture_pipeline = cogl_context_get_named_pipeline (ctx,
+ &texture_pipeline_key);
+
+ if (G_UNLIKELY (texture_pipeline == NULL))
+ {
+ CoglSnippet *snippet;
+
+ snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
+ "",
+ "if (cogl_color_out.a > 0.0)\n"
+ " cogl_color_out.a = 1.0;");
+
+ texture_pipeline = cogl_pipeline_new (ctx);
+ cogl_pipeline_add_snippet (texture_pipeline, snippet);
+ cogl_object_unref (snippet);
+
+ cogl_context_set_named_pipeline (ctx,
+ &texture_pipeline_key,
+ texture_pipeline);
+ }
+
+ /* No need to unref texture_pipeline since the named pipeline hash
+ * doesn't change its ref count from 1. Also no need to copy texture_pipeline
+ * since we'll be completely finished with it after clutter_paint_node_paint.
+ */
+
+ cogl_pipeline_set_layer_texture (texture_pipeline, 0, src_texture);
+ texture_node = clutter_pipeline_node_new (texture_pipeline);
+ clutter_paint_node_add_child (blur_node, texture_node);
+ clutter_paint_node_add_rectangle (texture_node,
+ &(ClutterActorBox) {
+ .x1 = sampling_radius,
+ .y1 = sampling_radius,
+ .x2 = src_width + sampling_radius,
+ .y2 = src_height + sampling_radius,
+ });
+
+ paint_context =
+ clutter_paint_context_new_for_framebuffer (fb, NULL, CLUTTER_PAINT_FLAG_NONE);
+ clutter_paint_node_paint (blur_node, paint_context);
+ clutter_paint_context_destroy (paint_context);
+
+ if (G_UNLIKELY (shadow_pipeline_template == NULL))
+ {
+ shadow_pipeline_template = cogl_pipeline_new (ctx);
+
+ /* We set up the pipeline to blend the shadow texture with the combine
+ * constant, but defer setting the latter until painting, so that we can
+ * take the actor's overall opacity into account. */
+ cogl_pipeline_set_layer_combine (shadow_pipeline_template, 0,
+ "RGBA = MODULATE (CONSTANT, TEXTURE[A])",
+ NULL);
+ }
+
+ pipeline = cogl_pipeline_copy (shadow_pipeline_template);
+ cogl_pipeline_set_layer_texture (pipeline, 0, texture);
+
+ cogl_clear_object (&texture);
+
+ return pipeline;
+}
+
+CoglPipeline *
+_st_create_shadow_pipeline_from_actor (StShadow *shadow_spec,
+ ClutterActor *actor)
+{
+ ClutterContent *image = NULL;
+ CoglPipeline *shadow_pipeline = NULL;
+ float resource_scale;
+ float width, height;
+ ClutterPaintContext *paint_context;
+
+ g_return_val_if_fail (clutter_actor_has_allocation (actor), NULL);
+
+ clutter_actor_get_size (actor, &width, &height);
+
+ if (width == 0 || height == 0)
+ return NULL;
+
+ resource_scale = clutter_actor_get_resource_scale (actor);
+
+ width = ceilf (width * resource_scale);
+ height = ceilf (height * resource_scale);
+
+ image = clutter_actor_get_content (actor);
+ if (image && CLUTTER_IS_IMAGE (image))
+ {
+ CoglTexture *texture;
+
+ texture = clutter_image_get_texture (CLUTTER_IMAGE (image));
+ if (texture &&
+ cogl_texture_get_width (texture) == width &&
+ cogl_texture_get_height (texture) == height)
+ shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, texture,
+ resource_scale);
+ }
+
+ if (shadow_pipeline == NULL)
+ {
+ CoglTexture *buffer;
+ CoglOffscreen *offscreen;
+ CoglFramebuffer *fb;
+ CoglContext *ctx;
+ CoglColor clear_color;
+ GError *catch_error = NULL;
+ float x, y;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ buffer = cogl_texture_2d_new_with_size (ctx, width, height);
+
+ if (buffer == NULL)
+ return NULL;
+
+ offscreen = cogl_offscreen_new_with_texture (buffer);
+ fb = COGL_FRAMEBUFFER (offscreen);
+
+ if (!cogl_framebuffer_allocate (fb, &catch_error))
+ {
+ g_error_free (catch_error);
+ g_object_unref (offscreen);
+ cogl_object_unref (buffer);
+ return NULL;
+ }
+
+ cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0);
+ clutter_actor_get_position (actor, &x, &y);
+ x *= resource_scale;
+ y *= resource_scale;
+
+ cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR, &clear_color);
+ cogl_framebuffer_translate (fb, -x, -y, 0);
+ cogl_framebuffer_orthographic (fb, 0, 0, width, height, 0, 1.0);
+ cogl_framebuffer_scale (fb, resource_scale, resource_scale, 1);
+
+ clutter_actor_set_opacity_override (actor, 255);
+
+ paint_context =
+ clutter_paint_context_new_for_framebuffer (fb, NULL,
+ CLUTTER_PAINT_FLAG_NONE);
+ clutter_actor_paint (actor, paint_context);
+ clutter_paint_context_destroy (paint_context);
+
+ clutter_actor_set_opacity_override (actor, -1);
+
+ g_object_unref (fb);
+
+ shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, buffer,
+ resource_scale);
+
+ cogl_object_unref (buffer);
+ }
+
+ return shadow_pipeline;
+}
+
+/**
+ * _st_create_shadow_cairo_pattern:
+ * @shadow_spec: the definition of the shadow
+ * @src_pattern: surface pattern for which we create the shadow
+ * (must be a surface pattern)
+ *
+ * This is a utility function for creating shadows used by
+ * st-theme-node.c; it's in this file to share the gaussian
+ * blur implementation. The usage of this function is quite different
+ * depending on whether shadow_spec->inset is %TRUE or not. If
+ * shadow_spec->inset is %TRUE, the caller should pass in a @src_pattern
+ * which is the <i>inverse</i> of what they want shadowed, and must take
+ * care of the spread and offset from the shadow spec themselves. If
+ * shadow_spec->inset is %FALSE then the caller should pass in what they
+ * want shadowed directly, and this function takes care of the spread and
+ * the offset.
+ */
+cairo_pattern_t *
+_st_create_shadow_cairo_pattern (StShadow *shadow_spec_in,
+ cairo_pattern_t *src_pattern)
+{
+ g_autoptr(StShadow) shadow_spec = NULL;
+ static cairo_user_data_key_t shadow_pattern_user_data;
+ cairo_t *cr;
+ cairo_surface_t *src_surface;
+ cairo_surface_t *surface_in;
+ cairo_surface_t *surface_out;
+ cairo_pattern_t *dst_pattern;
+ guchar *pixels_in, *pixels_out;
+ gint width_in, height_in, rowstride_in;
+ gint width_out, height_out;
+ size_t rowstride_out;
+ cairo_matrix_t shadow_matrix;
+ double xscale_in, yscale_in;
+ int i, j;
+
+ g_return_val_if_fail (shadow_spec_in != NULL, NULL);
+ g_return_val_if_fail (src_pattern != NULL, NULL);
+
+ if (cairo_pattern_get_surface (src_pattern, &src_surface) != CAIRO_STATUS_SUCCESS)
+ /* The most likely reason we can't get the pattern is that sizing went hairwire
+ * and the caller tried to create a surface too big for memory, leaving us with
+ * a pattern in an error state; we return a transparent pattern for the shadow.
+ */
+ return cairo_pattern_create_rgba(1.0, 1.0, 1.0, 0.0);
+
+ width_in = cairo_image_surface_get_width (src_surface);
+ height_in = cairo_image_surface_get_height (src_surface);
+
+ cairo_surface_get_device_scale (src_surface, &xscale_in, &yscale_in);
+
+ if (xscale_in != 1.0 || yscale_in != 1.0)
+ {
+ /* Scale the shadow specifications in a temporary copy so that
+ * we can work everywhere in absolute surface coordinates */
+ double scale = (xscale_in + yscale_in) / 2.0;
+ shadow_spec = st_shadow_new (&shadow_spec_in->color,
+ shadow_spec_in->xoffset * xscale_in,
+ shadow_spec_in->yoffset * yscale_in,
+ shadow_spec_in->blur * scale,
+ shadow_spec_in->spread * scale,
+ shadow_spec_in->inset);
+ }
+ else
+ {
+ shadow_spec = st_shadow_ref (shadow_spec_in);
+ }
+
+ /* We want the output to be a color agnostic alpha mask,
+ * so we need to strip the color channels from the input
+ */
+ if (cairo_image_surface_get_format (src_surface) != CAIRO_FORMAT_A8)
+ {
+ surface_in = cairo_image_surface_create (CAIRO_FORMAT_A8,
+ width_in, height_in);
+
+ cr = cairo_create (surface_in);
+ cairo_set_source_surface (cr, src_surface, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+ }
+ else
+ {
+ surface_in = cairo_surface_reference (src_surface);
+ }
+
+ pixels_in = cairo_image_surface_get_data (surface_in);
+ rowstride_in = cairo_image_surface_get_stride (surface_in);
+
+ pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
+ shadow_spec->blur,
+ &width_out, &height_out, &rowstride_out);
+ cairo_surface_destroy (surface_in);
+
+ /* Invert pixels for inset shadows */
+ if (shadow_spec->inset)
+ {
+ for (j = 0; j < height_out; j++)
+ {
+ guchar *p = pixels_out + rowstride_out * j;
+ for (i = 0; i < width_out; i++, p++)
+ *p = ~*p;
+ }
+ }
+
+ surface_out = cairo_image_surface_create_for_data (pixels_out,
+ CAIRO_FORMAT_A8,
+ width_out,
+ height_out,
+ rowstride_out);
+ cairo_surface_set_device_scale (surface_out, xscale_in, yscale_in);
+ cairo_surface_set_user_data (surface_out, &shadow_pattern_user_data,
+ pixels_out, (cairo_destroy_func_t) g_free);
+
+ dst_pattern = cairo_pattern_create_for_surface (surface_out);
+ cairo_surface_destroy (surface_out);
+
+ cairo_pattern_get_matrix (src_pattern, &shadow_matrix);
+
+ if (shadow_spec->inset)
+ {
+ /* Scale the matrix in surface absolute coordinates */
+ cairo_matrix_scale (&shadow_matrix, 1.0 / xscale_in, 1.0 / yscale_in);
+
+ /* For inset shadows, offsets and spread radius have already been
+ * applied to the original pattern, so all left to do is shift the
+ * blurred image left, so that it aligns centered under the
+ * unblurred one
+ */
+ cairo_matrix_translate (&shadow_matrix,
+ (width_out - width_in) / 2.0,
+ (height_out - height_in) / 2.0);
+
+ /* Scale back the matrix in original coordinates */
+ cairo_matrix_scale (&shadow_matrix, xscale_in, yscale_in);
+
+ cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
+ return dst_pattern;
+ }
+
+ /* Read all the code from the cairo_pattern_set_matrix call
+ * at the end of this function to here from bottom to top,
+ * because each new affine transformation is applied in
+ * front of all the previous ones */
+
+ /* 6. Invert the matrix back */
+ cairo_matrix_invert (&shadow_matrix);
+
+ /* Scale the matrix in surface absolute coordinates */
+ cairo_matrix_scale (&shadow_matrix, 1.0 / xscale_in, 1.0 / yscale_in);
+
+ /* 5. Adjust based on specified offsets */
+ cairo_matrix_translate (&shadow_matrix,
+ shadow_spec->xoffset,
+ shadow_spec->yoffset);
+
+ /* 4. Recenter the newly scaled image */
+ cairo_matrix_translate (&shadow_matrix,
+ - shadow_spec->spread,
+ - shadow_spec->spread);
+
+ /* 3. Scale up the blurred image to fill the spread */
+ cairo_matrix_scale (&shadow_matrix,
+ (width_in + 2.0 * shadow_spec->spread) / width_in,
+ (height_in + 2.0 * shadow_spec->spread) / height_in);
+
+ /* 2. Shift the blurred image left, so that it aligns centered
+ * under the unblurred one */
+ cairo_matrix_translate (&shadow_matrix,
+ - (width_out - width_in) / 2.0,
+ - (height_out - height_in) / 2.0);
+
+ /* Scale back the matrix in scaled coordinates */
+ cairo_matrix_scale (&shadow_matrix, xscale_in, yscale_in);
+
+ /* 1. Invert the matrix so we can work with it in pattern space
+ */
+ cairo_matrix_invert (&shadow_matrix);
+
+ cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
+
+ return dst_pattern;
+}
+
+void
+_st_paint_shadow_with_opacity (StShadow *shadow_spec,
+ CoglFramebuffer *framebuffer,
+ CoglPipeline *shadow_pipeline,
+ ClutterActorBox *box,
+ guint8 paint_opacity)
+{
+ ClutterActorBox shadow_box;
+ CoglColor color;
+
+ g_return_if_fail (shadow_spec != NULL);
+ g_return_if_fail (shadow_pipeline != NULL);
+
+ st_shadow_get_box (shadow_spec, box, &shadow_box);
+
+ cogl_color_init_from_4ub (&color,
+ shadow_spec->color.red * paint_opacity / 255,
+ shadow_spec->color.green * paint_opacity / 255,
+ shadow_spec->color.blue * paint_opacity / 255,
+ shadow_spec->color.alpha * paint_opacity / 255);
+ cogl_color_premultiply (&color);
+ cogl_pipeline_set_layer_combine_constant (shadow_pipeline, 0, &color);
+ cogl_framebuffer_draw_rectangle (framebuffer,
+ shadow_pipeline,
+ shadow_box.x1, shadow_box.y1,
+ shadow_box.x2, shadow_box.y2);
+}
diff --git a/src/st/st-private.h b/src/st/st-private.h
new file mode 100644
index 0000000..3f1fd12
--- /dev/null
+++ b/src/st/st-private.h
@@ -0,0 +1,75 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-private.h: Private declarations and functions
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_PRIVATE_H__
+#define __ST_PRIVATE_H__
+
+#include <glib.h>
+#include <cairo.h>
+#include "st-widget.h"
+#include "st-bin.h"
+#include "st-shadow.h"
+
+G_BEGIN_DECLS
+
+#define I_(str) (g_intern_static_string ((str)))
+
+#define ST_PARAM_READABLE (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)
+#define ST_PARAM_WRITABLE (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)
+#define ST_PARAM_READWRITE (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+
+G_END_DECLS
+
+ClutterActor *_st_widget_get_dnd_clone (StWidget *widget);
+
+void _st_actor_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gboolean y_fill,
+ gfloat *min_width_p,
+ gfloat *natural_width_p);
+void _st_actor_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gboolean x_fill,
+ gfloat *min_height_p,
+ gfloat *natural_height_p);
+
+void _st_set_text_from_style (ClutterText *text,
+ StThemeNode *theme_node);
+
+CoglPipeline * _st_create_texture_pipeline (CoglTexture *src_texture);
+
+/* Helper for widgets which need to draw additional shadows */
+CoglPipeline * _st_create_shadow_pipeline (StShadow *shadow_spec,
+ CoglTexture *src_texture,
+ float resource_scale);
+CoglPipeline * _st_create_shadow_pipeline_from_actor (StShadow *shadow_spec,
+ ClutterActor *actor);
+cairo_pattern_t *_st_create_shadow_cairo_pattern (StShadow *shadow_spec,
+ cairo_pattern_t *src_pattern);
+
+void _st_paint_shadow_with_opacity (StShadow *shadow_spec,
+ CoglFramebuffer *framebuffer,
+ CoglPipeline *shadow_pipeline,
+ ClutterActorBox *box,
+ guint8 paint_opacity);
+
+#endif /* __ST_PRIVATE_H__ */
diff --git a/src/st/st-scroll-bar.c b/src/st/st-scroll-bar.c
new file mode 100644
index 0000000..72bcd55
--- /dev/null
+++ b/src/st/st-scroll-bar.c
@@ -0,0 +1,1014 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-bar.c: Scroll bar actor
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-scroll-bar
+ * @short_description: a user interface element to control scrollable areas.
+ *
+ * The #StScrollBar allows users to scroll scrollable actors, either by
+ * the step or page amount, or by manually dragging the handle.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <clutter/clutter.h>
+
+#include "st-scroll-bar.h"
+#include "st-bin.h"
+#include "st-enum-types.h"
+#include "st-private.h"
+#include "st-button.h"
+#include "st-settings.h"
+
+#define PAGING_INITIAL_REPEAT_TIMEOUT 500
+#define PAGING_SUBSEQUENT_REPEAT_TIMEOUT 200
+
+typedef struct _StScrollBarPrivate StScrollBarPrivate;
+struct _StScrollBarPrivate
+{
+ StAdjustment *adjustment;
+
+ gfloat x_origin;
+ gfloat y_origin;
+
+ ClutterInputDevice *grab_device;
+ ClutterGrab *grab;
+
+ ClutterActor *trough;
+ ClutterActor *handle;
+
+ gfloat move_x;
+ gfloat move_y;
+
+ /* Trough-click handling. */
+ enum { NONE, UP, DOWN } paging_direction;
+ guint paging_source_id;
+ guint paging_event_no;
+
+ guint vertical : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StScrollBar, st_scroll_bar, ST_TYPE_WIDGET)
+
+#define ST_SCROLL_BAR_PRIVATE(sb) st_scroll_bar_get_instance_private (ST_SCROLL_BAR (sb))
+
+enum
+{
+ PROP_0,
+
+ PROP_ADJUSTMENT,
+ PROP_VERTICAL,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ SCROLL_START,
+ SCROLL_STOP,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+static gboolean
+handle_button_press_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *bar);
+
+static void stop_scrolling (StScrollBar *bar);
+
+static void
+st_scroll_bar_set_vertical (StScrollBar *bar,
+ gboolean vertical)
+{
+ StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (bar);
+
+ if (priv->vertical == vertical)
+ return;
+
+ priv->vertical = vertical;
+
+ if (priv->vertical)
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->handle),
+ "vhandle");
+ else
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->handle),
+ "hhandle");
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (bar));
+ g_object_notify_by_pspec (G_OBJECT (bar), props[PROP_VERTICAL]);
+}
+
+static void
+st_scroll_bar_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_ADJUSTMENT:
+ g_value_set_object (value, priv->adjustment);
+ break;
+
+ case PROP_VERTICAL:
+ g_value_set_boolean (value, priv->vertical);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_bar_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_ADJUSTMENT:
+ st_scroll_bar_set_adjustment (bar, g_value_get_object (value));
+ break;
+
+ case PROP_VERTICAL:
+ st_scroll_bar_set_vertical (bar, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_bar_dispose (GObject *gobject)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (gobject);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+
+ if (priv->adjustment)
+ st_scroll_bar_set_adjustment (bar, NULL);
+
+ if (priv->handle)
+ {
+ clutter_actor_destroy (priv->handle);
+ priv->handle = NULL;
+ }
+
+ if (priv->trough)
+ {
+ clutter_actor_destroy (priv->trough);
+ priv->trough = NULL;
+ }
+
+ G_OBJECT_CLASS (st_scroll_bar_parent_class)->dispose (gobject);
+}
+
+static void
+st_scroll_bar_unmap (ClutterActor *actor)
+{
+ CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->unmap (actor);
+
+ stop_scrolling (ST_SCROLL_BAR (actor));
+}
+
+static void
+scroll_bar_allocate_children (StScrollBar *bar,
+ const ClutterActorBox *box)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (bar));
+ ClutterActorBox content_box, trough_box;
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ trough_box.x1 = content_box.x1;
+ trough_box.y1 = content_box.y1;
+ trough_box.x2 = content_box.x2;
+ trough_box.y2 = content_box.y2;
+ clutter_actor_allocate (priv->trough, &trough_box);
+
+ if (priv->adjustment)
+ {
+ float handle_size, position, avail_size;
+ gdouble value, lower, upper, page_size, increment, min_size, max_size;
+ ClutterActorBox handle_box = { 0, };
+
+ st_adjustment_get_values (priv->adjustment,
+ &value,
+ &lower,
+ &upper,
+ NULL,
+ NULL,
+ &page_size);
+
+ if ((upper == lower)
+ || (page_size >= (upper - lower)))
+ increment = 1.0;
+ else
+ increment = page_size / (upper - lower);
+
+ min_size = 32.;
+ st_theme_node_lookup_length (theme_node, "min-size", FALSE, &min_size);
+ max_size = G_MAXINT16;
+ st_theme_node_lookup_length (theme_node, "max-size", FALSE, &max_size);
+
+ if (upper - lower - page_size <= 0)
+ position = 0;
+ else
+ position = (value - lower) / (upper - lower - page_size);
+
+ if (priv->vertical)
+ {
+ avail_size = content_box.y2 - content_box.y1;
+ handle_size = increment * avail_size;
+ handle_size = CLAMP (handle_size, min_size, max_size);
+
+ handle_box.x1 = content_box.x1;
+ handle_box.y1 = content_box.y1 + position * (avail_size - handle_size);
+
+ handle_box.x2 = content_box.x2;
+ handle_box.y2 = handle_box.y1 + handle_size;
+ }
+ else
+ {
+ ClutterTextDirection direction;
+
+ avail_size = content_box.x2 - content_box.x1;
+ handle_size = increment * avail_size;
+ handle_size = CLAMP (handle_size, min_size, max_size);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (bar));
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ handle_box.x2 = content_box.x2 - position * (avail_size - handle_size);
+ handle_box.x1 = handle_box.x2 - handle_size;
+ }
+ else
+ {
+ handle_box.x1 = content_box.x1 + position * (avail_size - handle_size);
+ handle_box.x2 = handle_box.x1 + handle_size;
+ }
+
+ handle_box.y1 = content_box.y1;
+ handle_box.y2 = content_box.y2;
+ }
+
+ clutter_actor_allocate (priv->handle, &handle_box);
+ }
+}
+
+static void
+st_scroll_bar_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (self);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ gfloat trough_min_width, trough_natural_width;
+ gfloat handle_min_width, handle_natural_width;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ _st_actor_get_preferred_width (priv->trough, for_height, TRUE,
+ &trough_min_width, &trough_natural_width);
+
+ _st_actor_get_preferred_width (priv->handle, for_height, TRUE,
+ &handle_min_width, &handle_natural_width);
+
+ if (priv->vertical)
+ {
+ if (min_width_p)
+ *min_width_p = MAX (trough_min_width, handle_min_width);
+
+ if (natural_width_p)
+ *natural_width_p = MAX (trough_natural_width, handle_natural_width);
+ }
+ else
+ {
+ if (min_width_p)
+ *min_width_p = trough_min_width + handle_min_width;
+
+ if (natural_width_p)
+ *natural_width_p = trough_natural_width + handle_natural_width;
+ }
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_scroll_bar_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (self);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ gfloat trough_min_height, trough_natural_height;
+ gfloat handle_min_height, handle_natural_height;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ _st_actor_get_preferred_height (priv->trough, for_width, TRUE,
+ &trough_min_height, &trough_natural_height);
+
+ _st_actor_get_preferred_height (priv->handle, for_width, TRUE,
+ &handle_min_height, &handle_natural_height);
+
+ if (priv->vertical)
+ {
+ if (min_height_p)
+ *min_height_p = trough_min_height + handle_min_height;
+
+ if (natural_height_p)
+ *natural_height_p = trough_natural_height + handle_natural_height;
+ }
+ else
+ {
+ if (min_height_p)
+ *min_height_p = MAX (trough_min_height, handle_min_height);
+
+ if (natural_height_p)
+ *natural_height_p = MAX (trough_natural_height, handle_natural_height);
+ }
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_scroll_bar_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (actor);
+
+ clutter_actor_set_allocation (actor, box);
+
+ scroll_bar_allocate_children (bar, box);
+}
+
+static void
+scroll_bar_update_positions (StScrollBar *bar)
+{
+ ClutterActorBox box;
+
+ /* Due to a change in the adjustments, we need to reposition our
+ * children; since adjustments changes can come from allocation
+ * changes in the scrolled area, we can't just queue a new relayout -
+ * we may already be in a relayout cycle. On the other hand, if
+ * a relayout is already queued, we can't just go ahead and allocate
+ * our children, since we don't have a valid allocation, and calling
+ * clutter_actor_get_allocation_box() will trigger an immediate
+ * stage relayout. So what we do is go ahead and immediately
+ * allocate our children if we already have a valid allocation, and
+ * otherwise just wait for the queued relayout.
+ */
+ if (!clutter_actor_has_allocation (CLUTTER_ACTOR (bar)))
+ return;
+
+ clutter_actor_get_allocation_box (CLUTTER_ACTOR (bar), &box);
+ scroll_bar_allocate_children (bar, &box);
+}
+
+static void
+bar_reactive_notify_cb (GObject *gobject,
+ GParamSpec *arg1,
+ gpointer user_data)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (gobject);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+
+ clutter_actor_set_reactive (priv->handle,
+ clutter_actor_get_reactive (CLUTTER_ACTOR (bar)));
+}
+
+static GObject*
+st_scroll_bar_constructor (GType type,
+ guint n_properties,
+ GObjectConstructParam *properties)
+{
+ GObjectClass *gobject_class;
+ GObject *obj;
+ StScrollBar *bar;
+
+ gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class);
+ obj = gobject_class->constructor (type, n_properties, properties);
+
+ bar = ST_SCROLL_BAR (obj);
+
+ g_signal_connect (bar, "notify::reactive",
+ G_CALLBACK (bar_reactive_notify_cb), NULL);
+
+ return obj;
+}
+
+static void
+adjust_with_direction (StAdjustment *adj,
+ ClutterScrollDirection direction)
+{
+ gdouble delta;
+
+ switch (direction)
+ {
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_LEFT:
+ delta = -1.0;
+ break;
+ case CLUTTER_SCROLL_RIGHT:
+ case CLUTTER_SCROLL_DOWN:
+ delta = 1.0;
+ break;
+ case CLUTTER_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ st_adjustment_adjust_for_scroll_event (adj, delta);
+}
+
+static gboolean
+st_scroll_bar_scroll_event (ClutterActor *actor,
+ ClutterScrollEvent *event)
+{
+ StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (actor);
+ ClutterTextDirection direction;
+ ClutterScrollDirection scroll_dir;
+
+ if (clutter_event_is_pointer_emulated ((ClutterEvent *) event))
+ return TRUE;
+
+ direction = clutter_actor_get_text_direction (actor);
+ scroll_dir = event->direction;
+
+ switch (scroll_dir)
+ {
+ case CLUTTER_SCROLL_SMOOTH:
+ {
+ gdouble delta_x, delta_y;
+ clutter_event_get_scroll_delta ((ClutterEvent *)event, &delta_x, &delta_y);
+
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ delta_x *= -1;
+
+ if (priv->vertical)
+ st_adjustment_adjust_for_scroll_event (priv->adjustment, delta_y);
+ else
+ st_adjustment_adjust_for_scroll_event (priv->adjustment, delta_x);
+ }
+ break;
+ case CLUTTER_SCROLL_LEFT:
+ case CLUTTER_SCROLL_RIGHT:
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ scroll_dir = scroll_dir == CLUTTER_SCROLL_LEFT ? CLUTTER_SCROLL_RIGHT
+ : CLUTTER_SCROLL_LEFT;
+ /* Fall through */
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_DOWN:
+ adjust_with_direction (priv->adjustment, scroll_dir);
+ break;
+ default:
+ g_return_val_if_reached (FALSE);
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+st_scroll_bar_class_init (StScrollBarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ object_class->get_property = st_scroll_bar_get_property;
+ object_class->set_property = st_scroll_bar_set_property;
+ object_class->dispose = st_scroll_bar_dispose;
+ object_class->constructor = st_scroll_bar_constructor;
+
+ actor_class->get_preferred_width = st_scroll_bar_get_preferred_width;
+ actor_class->get_preferred_height = st_scroll_bar_get_preferred_height;
+ actor_class->allocate = st_scroll_bar_allocate;
+ actor_class->scroll_event = st_scroll_bar_scroll_event;
+ actor_class->unmap = st_scroll_bar_unmap;
+
+ /**
+ * StScrollBar:adjustment:
+ *
+ * The #StAdjustment controlling the #StScrollBar.
+ */
+ props[PROP_ADJUSTMENT] =
+ g_param_spec_object ("adjustment", "Adjustment", "The adjustment",
+ ST_TYPE_ADJUSTMENT,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollBar:vertical:
+ *
+ * Whether the #StScrollBar is vertical. If %FALSE it is horizontal.
+ */
+ props[PROP_VERTICAL] =
+ g_param_spec_boolean ("vertical",
+ "Vertical Orientation",
+ "Vertical Orientation",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+
+ /**
+ * StScrollBar::scroll-start:
+ * @bar: a #StScrollBar
+ *
+ * Emitted when the #StScrollBar begins scrolling.
+ */
+ signals[SCROLL_START] =
+ g_signal_new ("scroll-start",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StScrollBarClass, scroll_start),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StScrollBar::scroll-stop:
+ * @bar: a #StScrollBar
+ *
+ * Emitted when the #StScrollBar finishes scrolling.
+ */
+ signals[SCROLL_STOP] =
+ g_signal_new ("scroll-stop",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StScrollBarClass, scroll_stop),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+move_slider (StScrollBar *bar,
+ gfloat x,
+ gfloat y)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ ClutterTextDirection direction;
+ gdouble position, lower, upper, page_size;
+ gfloat ux, uy, pos, size;
+
+ if (!priv->adjustment)
+ return;
+
+ if (!clutter_actor_transform_stage_point (priv->trough, x, y, &ux, &uy))
+ return;
+
+ if (priv->vertical)
+ size = clutter_actor_get_height (priv->trough)
+ - clutter_actor_get_height (priv->handle);
+ else
+ size = clutter_actor_get_width (priv->trough)
+ - clutter_actor_get_width (priv->handle);
+
+ if (size == 0)
+ return;
+
+ if (priv->vertical)
+ pos = uy - priv->y_origin;
+ else
+ pos = ux - priv->x_origin;
+ pos = CLAMP (pos, 0, size);
+
+ st_adjustment_get_values (priv->adjustment,
+ NULL,
+ &lower,
+ &upper,
+ NULL,
+ NULL,
+ &page_size);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (bar));
+ if (!priv->vertical && direction == CLUTTER_TEXT_DIRECTION_RTL)
+ pos = size - pos;
+
+ position = ((pos / size)
+ * (upper - lower - page_size))
+ + lower;
+
+ st_adjustment_set_value (priv->adjustment, position);
+}
+
+static void
+stop_scrolling (StScrollBar *bar)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ if (!priv->grab_device)
+ return;
+
+ st_widget_remove_style_pseudo_class (ST_WIDGET (priv->handle), "active");
+
+ if (priv->grab)
+ {
+ clutter_grab_dismiss (priv->grab);
+ g_clear_pointer (&priv->grab, clutter_grab_unref);
+ }
+
+ priv->grab_device = NULL;
+ g_signal_emit (bar, signals[SCROLL_STOP], 0);
+}
+
+static gboolean
+handle_motion_event_cb (ClutterActor *trough,
+ ClutterMotionEvent *event,
+ StScrollBar *bar)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ if (!priv->grab_device)
+ return FALSE;
+
+ move_slider (bar, event->x, event->y);
+ return TRUE;
+}
+
+static gboolean
+handle_button_release_event_cb (ClutterActor *trough,
+ ClutterButtonEvent *event,
+ StScrollBar *bar)
+{
+ if (event->button != 1)
+ return FALSE;
+
+ stop_scrolling (bar);
+ return TRUE;
+}
+
+static gboolean
+handle_button_press_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *bar)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ ClutterInputDevice *device = clutter_event_get_device ((ClutterEvent*) event);
+ ClutterActor *stage;
+
+ if (event->button != 1)
+ return FALSE;
+
+ if (!clutter_actor_transform_stage_point (priv->handle,
+ event->x,
+ event->y,
+ &priv->x_origin,
+ &priv->y_origin))
+ return FALSE;
+
+ st_widget_add_style_pseudo_class (ST_WIDGET (priv->handle), "active");
+
+ /* Account for the scrollbar-trough-handle nesting. */
+ priv->x_origin += clutter_actor_get_x (priv->trough);
+ priv->y_origin += clutter_actor_get_y (priv->trough);
+
+ g_assert (!priv->grab_device);
+
+ stage = clutter_actor_get_stage (actor);
+ priv->grab = clutter_stage_grab (CLUTTER_STAGE (stage), priv->handle);
+ priv->grab_device = device;
+ g_signal_emit (bar, signals[SCROLL_START], 0);
+
+ return TRUE;
+}
+
+static gboolean
+trough_paging_cb (StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+ ClutterTextDirection direction;
+ g_autoptr (ClutterTransition) transition = NULL;
+ StSettings *settings;
+ gfloat handle_pos, event_pos, tx, ty;
+ gdouble value, new_value;
+ gdouble page_increment;
+ gdouble slow_down_factor;
+ gboolean ret;
+
+ gulong mode;
+
+ if (priv->paging_event_no == 0)
+ {
+ /* Scroll on after initial timeout. */
+ mode = CLUTTER_EASE_OUT_CUBIC;
+ ret = FALSE;
+ priv->paging_event_no = 1;
+ priv->paging_source_id = g_timeout_add (
+ PAGING_INITIAL_REPEAT_TIMEOUT,
+ (GSourceFunc) trough_paging_cb,
+ self);
+ g_source_set_name_by_id (priv->paging_source_id, "[gnome-shell] trough_paging_cb");
+ }
+ else if (priv->paging_event_no == 1)
+ {
+ /* Scroll on after subsequent timeout. */
+ ret = FALSE;
+ mode = CLUTTER_EASE_IN_CUBIC;
+ priv->paging_event_no = 2;
+ priv->paging_source_id = g_timeout_add (
+ PAGING_SUBSEQUENT_REPEAT_TIMEOUT,
+ (GSourceFunc) trough_paging_cb,
+ self);
+ g_source_set_name_by_id (priv->paging_source_id, "[gnome-shell] trough_paging_cb");
+ }
+ else
+ {
+ /* Keep scrolling. */
+ ret = TRUE;
+ mode = CLUTTER_LINEAR;
+ priv->paging_event_no++;
+ }
+
+ /* Do the scrolling */
+ st_adjustment_get_values (priv->adjustment,
+ &value, NULL, NULL,
+ NULL, &page_increment, NULL);
+
+ if (priv->vertical)
+ handle_pos = clutter_actor_get_y (priv->handle);
+ else
+ handle_pos = clutter_actor_get_x (priv->handle);
+
+ clutter_actor_transform_stage_point (CLUTTER_ACTOR (priv->trough),
+ priv->move_x,
+ priv->move_y,
+ &tx, &ty);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (self));
+ if (!priv->vertical && direction == CLUTTER_TEXT_DIRECTION_RTL)
+ page_increment *= -1;
+
+ if (priv->vertical)
+ event_pos = ty;
+ else
+ event_pos = tx;
+
+ if (event_pos > handle_pos)
+ {
+ if (priv->paging_direction == NONE)
+ {
+ /* Remember direction. */
+ priv->paging_direction = DOWN;
+ }
+ if (priv->paging_direction == UP)
+ {
+ /* Scrolled far enough. */
+ return FALSE;
+ }
+ new_value = value + page_increment;
+ }
+ else
+ {
+ if (priv->paging_direction == NONE)
+ {
+ /* Remember direction. */
+ priv->paging_direction = UP;
+ }
+ if (priv->paging_direction == DOWN)
+ {
+ /* Scrolled far enough. */
+ return FALSE;
+ }
+ new_value = value - page_increment;
+ }
+
+ /* Stop existing transition, if one exists */
+ st_adjustment_remove_transition (priv->adjustment, "value");
+
+ settings = st_settings_get ();
+ g_object_get (settings, "slow-down-factor", &slow_down_factor, NULL);
+
+ /* FIXME: Creating a new transition for each scroll is probably not the best
+ * idea, but it's a lot less involved than extending the current animation */
+ transition = g_object_new (CLUTTER_TYPE_PROPERTY_TRANSITION,
+ "property-name", "value",
+ "interval", clutter_interval_new (G_TYPE_DOUBLE, value, new_value),
+ "duration", (guint)(PAGING_SUBSEQUENT_REPEAT_TIMEOUT * slow_down_factor),
+ "progress-mode", mode,
+ "remove-on-complete", TRUE,
+ NULL);
+ st_adjustment_add_transition (priv->adjustment, "value", transition);
+
+ return ret;
+}
+
+static gboolean
+trough_button_press_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *self)
+{
+ StScrollBarPrivate *priv;
+
+ g_return_val_if_fail (self, FALSE);
+
+ if (event->button != 1)
+ return FALSE;
+
+ priv = st_scroll_bar_get_instance_private (self);
+ if (priv->adjustment == NULL)
+ return FALSE;
+
+ priv->move_x = event->x;
+ priv->move_y = event->y;
+ priv->paging_direction = NONE;
+ priv->paging_event_no = 0;
+ trough_paging_cb (self);
+
+ return TRUE;
+}
+
+static gboolean
+trough_button_release_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ if (event->button != 1)
+ return FALSE;
+
+ g_clear_handle_id (&priv->paging_source_id, g_source_remove);
+
+ return TRUE;
+}
+
+static gboolean
+trough_leave_event_cb (ClutterActor *actor,
+ ClutterEvent *event,
+ StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ if (priv->paging_source_id)
+ {
+ g_clear_handle_id (&priv->paging_source_id, g_source_remove);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+st_scroll_bar_notify_reactive (StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ gboolean reactive = clutter_actor_get_reactive (CLUTTER_ACTOR (self));
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (priv->trough), reactive);
+ clutter_actor_set_reactive (CLUTTER_ACTOR (priv->handle), reactive);
+}
+
+static void
+st_scroll_bar_init (StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ priv->trough = (ClutterActor *) st_bin_new ();
+ clutter_actor_set_reactive ((ClutterActor *) priv->trough, TRUE);
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->trough), "trough");
+ clutter_actor_add_child (CLUTTER_ACTOR (self),
+ CLUTTER_ACTOR (priv->trough));
+ g_signal_connect (priv->trough, "button-press-event",
+ G_CALLBACK (trough_button_press_event_cb), self);
+ g_signal_connect (priv->trough, "button-release-event",
+ G_CALLBACK (trough_button_release_event_cb), self);
+ g_signal_connect (priv->trough, "leave-event",
+ G_CALLBACK (trough_leave_event_cb), self);
+
+ priv->handle = (ClutterActor *) st_button_new ();
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->handle), "hhandle");
+ clutter_actor_add_child (CLUTTER_ACTOR (self),
+ CLUTTER_ACTOR (priv->handle));
+ g_signal_connect (priv->handle, "button-press-event",
+ G_CALLBACK (handle_button_press_event_cb), self);
+ g_signal_connect (priv->handle, "button-release-event",
+ G_CALLBACK (handle_button_release_event_cb), self);
+ g_signal_connect (priv->handle, "motion-event",
+ G_CALLBACK (handle_motion_event_cb), self);
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
+
+ g_signal_connect (self, "notify::reactive",
+ G_CALLBACK (st_scroll_bar_notify_reactive), NULL);
+}
+
+StWidget *
+st_scroll_bar_new (StAdjustment *adjustment)
+{
+ return g_object_new (ST_TYPE_SCROLL_BAR,
+ "adjustment", adjustment,
+ NULL);
+}
+
+static void
+on_notify_value (GObject *object,
+ GParamSpec *pspec,
+ StScrollBar *bar)
+{
+ scroll_bar_update_positions (bar);
+}
+
+static void
+on_changed (StAdjustment *adjustment,
+ StScrollBar *bar)
+{
+ scroll_bar_update_positions (bar);
+}
+
+void
+st_scroll_bar_set_adjustment (StScrollBar *bar,
+ StAdjustment *adjustment)
+{
+ StScrollBarPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_BAR (bar));
+
+ priv = st_scroll_bar_get_instance_private (bar);
+
+ if (adjustment == priv->adjustment)
+ return;
+
+ if (priv->adjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->adjustment,
+ on_notify_value,
+ bar);
+ g_signal_handlers_disconnect_by_func (priv->adjustment,
+ on_changed,
+ bar);
+ g_object_unref (priv->adjustment);
+ priv->adjustment = NULL;
+ }
+
+ if (adjustment)
+ {
+ priv->adjustment = g_object_ref (adjustment);
+
+ g_signal_connect (priv->adjustment, "notify::value",
+ G_CALLBACK (on_notify_value),
+ bar);
+ g_signal_connect (priv->adjustment, "changed",
+ G_CALLBACK (on_changed),
+ bar);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (bar));
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (bar), props[PROP_ADJUSTMENT]);
+}
+
+/**
+ * st_scroll_bar_get_adjustment:
+ * @bar: a #StScrollbar
+ *
+ * Gets the #StAdjustment that controls the current position of @bar.
+ *
+ * Returns: (transfer none): an #StAdjustment
+ */
+StAdjustment *
+st_scroll_bar_get_adjustment (StScrollBar *bar)
+{
+ g_return_val_if_fail (ST_IS_SCROLL_BAR (bar), NULL);
+
+ return ((StScrollBarPrivate *)ST_SCROLL_BAR_PRIVATE (bar))->adjustment;
+}
+
diff --git a/src/st/st-scroll-bar.h b/src/st/st-scroll-bar.h
new file mode 100644
index 0000000..2c69fdd
--- /dev/null
+++ b/src/st/st-scroll-bar.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-bar.h: Scroll bar actor
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SCROLL_BAR_H__
+#define __ST_SCROLL_BAR_H__
+
+#include <st/st-adjustment.h>
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLL_BAR (st_scroll_bar_get_type())
+G_DECLARE_DERIVABLE_TYPE (StScrollBar, st_scroll_bar, ST, SCROLL_BAR, StWidget)
+
+struct _StScrollBarClass
+{
+ StWidgetClass parent_class;
+
+ /* signals */
+ void (*scroll_start) (StScrollBar *bar);
+ void (*scroll_stop) (StScrollBar *bar);
+};
+
+StWidget *st_scroll_bar_new (StAdjustment *adjustment);
+
+void st_scroll_bar_set_adjustment (StScrollBar *bar,
+ StAdjustment *adjustment);
+StAdjustment *st_scroll_bar_get_adjustment (StScrollBar *bar);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLL_BAR_H__ */
diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c
new file mode 100644
index 0000000..77b1d0b
--- /dev/null
+++ b/src/st/st-scroll-view-fade.c
@@ -0,0 +1,461 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view-fade.h: Edge fade effect for StScrollView
+ *
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Adel Gadllah
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "st-private.h"
+#include "st-scroll-view-fade.h"
+#include "st-scroll-view.h"
+#include "st-widget.h"
+#include "st-theme-node.h"
+#include "st-scroll-bar.h"
+#include "st-scrollable.h"
+
+#include <clutter/clutter.h>
+#include <cogl/cogl.h>
+
+#define DEFAULT_FADE_OFFSET 68.0f
+
+#include "st-scroll-view-fade-generated.h"
+
+struct _StScrollViewFade
+{
+ ClutterShaderEffect parent_instance;
+
+ /* a back pointer to our actor, so that we can query it */
+ ClutterActor *actor;
+
+ StAdjustment *vadjustment;
+ StAdjustment *hadjustment;
+
+ guint fade_edges : 1;
+ guint extend_fade_area: 1;
+
+ ClutterMargin fade_margins;
+};
+
+G_DEFINE_TYPE (StScrollViewFade,
+ st_scroll_view_fade,
+ CLUTTER_TYPE_SHADER_EFFECT);
+
+enum {
+ PROP_0,
+
+ PROP_FADE_MARGINS,
+ PROP_FADE_EDGES,
+ PROP_EXTEND_FADE_AREA,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static CoglTexture *
+st_scroll_view_fade_create_texture (ClutterOffscreenEffect *effect,
+ gfloat min_width,
+ gfloat min_height)
+{
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ return COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, min_width, min_height));
+}
+
+static char *
+st_scroll_view_fade_get_static_shader_source (ClutterShaderEffect *effect)
+{
+ return g_strdup (st_scroll_view_fade_glsl);
+}
+
+
+static void
+st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect,
+ ClutterPaintNode *node,
+ ClutterPaintContext *paint_context)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect);
+ ClutterShaderEffect *shader = CLUTTER_SHADER_EFFECT (effect);
+ ClutterOffscreenEffectClass *parent;
+
+ gdouble value, lower, upper, page_size;
+ ClutterActor *vscroll = st_scroll_view_get_vscroll_bar (ST_SCROLL_VIEW (self->actor));
+ ClutterActor *hscroll = st_scroll_view_get_hscroll_bar (ST_SCROLL_VIEW (self->actor));
+ gboolean h_scroll_visible, v_scroll_visible, rtl;
+
+ ClutterActorBox allocation, content_box, paint_box;
+
+ float fade_area_topleft[2];
+ float fade_area_bottomright[2];
+ graphene_point3d_t verts[4];
+
+ clutter_actor_get_paint_box (self->actor, &paint_box);
+ clutter_actor_get_abs_allocation_vertices (self->actor, verts);
+
+ clutter_actor_get_allocation_box (self->actor, &allocation);
+ st_theme_node_get_content_box (st_widget_get_theme_node (ST_WIDGET (self->actor)),
+ (const ClutterActorBox *)&allocation, &content_box);
+
+ /*
+ * The FBO is based on the paint_volume's size which can be larger then the actual
+ * allocation, so we have to account for that when passing the positions
+ */
+ fade_area_topleft[0] = content_box.x1 + (verts[0].x - paint_box.x1);
+ fade_area_topleft[1] = content_box.y1 + (verts[0].y - paint_box.y1);
+ fade_area_bottomright[0] = content_box.x2 + (verts[3].x - paint_box.x2) + 1;
+ fade_area_bottomright[1] = content_box.y2 + (verts[3].y - paint_box.y2) + 1;
+
+ g_object_get (ST_SCROLL_VIEW (self->actor),
+ "hscrollbar-visible", &h_scroll_visible,
+ "vscrollbar-visible", &v_scroll_visible,
+ NULL);
+
+ if (v_scroll_visible)
+ {
+ if (clutter_actor_get_text_direction (self->actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ fade_area_topleft[0] += clutter_actor_get_width (vscroll);
+
+ fade_area_bottomright[0] -= clutter_actor_get_width (vscroll);
+ }
+
+ if (h_scroll_visible)
+ fade_area_bottomright[1] -= clutter_actor_get_height (hscroll);
+
+ if (self->fade_margins.left < 0)
+ fade_area_topleft[0] -= ABS (self->fade_margins.left);
+ if (self->fade_margins.right < 0)
+ fade_area_bottomright[0] += ABS (self->fade_margins.right);
+ if (self->fade_margins.top < 0)
+ fade_area_topleft[1] -= ABS (self->fade_margins.top);
+ if (self->fade_margins.bottom < 0)
+ fade_area_bottomright[1] += ABS (self->fade_margins.bottom);
+
+ st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ value = (value - lower) / (upper - page_size - lower);
+ clutter_shader_effect_set_uniform (shader, "fade_edges_top", G_TYPE_INT, 1, self->fade_edges ? value >= 0.0 : value > 0.0);
+ clutter_shader_effect_set_uniform (shader, "fade_edges_bottom", G_TYPE_INT, 1, self->fade_edges ? value <= 1.0 : value < 1.0);
+
+ st_adjustment_get_values (self->hadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ value = (value - lower) / (upper - page_size - lower);
+ rtl = clutter_actor_get_text_direction (self->actor) == CLUTTER_TEXT_DIRECTION_RTL;
+ clutter_shader_effect_set_uniform (shader, "fade_edges_left", G_TYPE_INT, 1,
+ self->fade_edges ?
+ value >= 0.0 :
+ (rtl ? value < 1.0 : value > 0.0));
+ clutter_shader_effect_set_uniform (shader, "fade_edges_right", G_TYPE_INT, 1,
+ self->fade_edges ?
+ value <= 1.0 :
+ (rtl ? value > 0.0 : value < 1.0));
+
+ clutter_shader_effect_set_uniform (shader, "extend_fade_area", G_TYPE_INT, 1, self->extend_fade_area);
+ clutter_shader_effect_set_uniform (shader, "fade_offset_top", G_TYPE_FLOAT, 1, ABS (self->fade_margins.top));
+ clutter_shader_effect_set_uniform (shader, "fade_offset_bottom", G_TYPE_FLOAT, 1, ABS (self->fade_margins.bottom));
+ clutter_shader_effect_set_uniform (shader, "fade_offset_left", G_TYPE_FLOAT, 1, ABS (self->fade_margins.left));
+ clutter_shader_effect_set_uniform (shader, "fade_offset_right", G_TYPE_FLOAT, 1, ABS (self->fade_margins.right));
+ clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0);
+ clutter_shader_effect_set_uniform (shader, "height", G_TYPE_FLOAT, 1, clutter_actor_get_height (self->actor));
+ clutter_shader_effect_set_uniform (shader, "width", G_TYPE_FLOAT, 1, clutter_actor_get_width (self->actor));
+ clutter_shader_effect_set_uniform (shader, "fade_area_topleft", CLUTTER_TYPE_SHADER_FLOAT, 2, fade_area_topleft);
+ clutter_shader_effect_set_uniform (shader, "fade_area_bottomright", CLUTTER_TYPE_SHADER_FLOAT, 2, fade_area_bottomright);
+
+ parent = CLUTTER_OFFSCREEN_EFFECT_CLASS (st_scroll_view_fade_parent_class);
+ parent->paint_target (effect, node, paint_context);
+}
+
+static void
+on_adjustment_changed (StAdjustment *adjustment,
+ ClutterEffect *effect)
+{
+ gdouble value, lower, upper, page_size;
+ gboolean needs_fade;
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect);
+
+ st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1);
+
+ if (!needs_fade)
+ {
+ st_adjustment_get_values (self->hadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1);
+ }
+
+ clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (effect), needs_fade);
+}
+
+static void
+st_scroll_view_fade_set_actor (ClutterActorMeta *meta,
+ ClutterActor *actor)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (meta);
+ ClutterActorMetaClass *parent;
+
+ g_return_if_fail (actor == NULL || ST_IS_SCROLL_VIEW (actor));
+
+ if (self->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->vadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->vadjustment = NULL;
+ }
+
+ if (self->hadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->hadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->hadjustment = NULL;
+ }
+
+
+ if (actor)
+ {
+ StScrollView *scroll_view = ST_SCROLL_VIEW (actor);
+ StScrollBar *vscroll = ST_SCROLL_BAR (st_scroll_view_get_vscroll_bar (scroll_view));
+ StScrollBar *hscroll = ST_SCROLL_BAR (st_scroll_view_get_hscroll_bar (scroll_view));
+ self->vadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (vscroll));
+ self->hadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (hscroll));
+
+ g_signal_connect (self->vadjustment, "changed",
+ G_CALLBACK (on_adjustment_changed),
+ self);
+
+ g_signal_connect (self->hadjustment, "changed",
+ G_CALLBACK (on_adjustment_changed),
+ self);
+
+ on_adjustment_changed (NULL, CLUTTER_EFFECT (self));
+ }
+
+ parent = CLUTTER_ACTOR_META_CLASS (st_scroll_view_fade_parent_class);
+ parent->set_actor (meta, actor);
+
+ /* we keep a back pointer here, to avoid going through the ActorMeta */
+ self->actor = clutter_actor_meta_get_actor (meta);
+}
+
+static void
+st_scroll_view_fade_dispose (GObject *gobject)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (gobject);
+
+ if (self->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->vadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->vadjustment = NULL;
+ }
+
+ if (self->hadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->hadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->hadjustment = NULL;
+ }
+
+ self->actor = NULL;
+
+ G_OBJECT_CLASS (st_scroll_view_fade_parent_class)->dispose (gobject);
+}
+
+static void
+st_scroll_view_set_fade_margins (StScrollViewFade *self,
+ ClutterMargin *fade_margins)
+{
+ if (self->fade_margins.left == fade_margins->left &&
+ self->fade_margins.right == fade_margins->right &&
+ self->fade_margins.top == fade_margins->top &&
+ self->fade_margins.bottom == fade_margins->bottom)
+ return;
+
+ self->fade_margins = *fade_margins;
+
+ if (self->actor != NULL)
+ clutter_actor_queue_redraw (self->actor);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FADE_MARGINS]);
+}
+
+static void
+st_scroll_view_fade_set_fade_edges (StScrollViewFade *self,
+ gboolean fade_edges)
+{
+ if (self->fade_edges == fade_edges)
+ return;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ self->fade_edges = fade_edges;
+
+ if (self->actor != NULL)
+ clutter_actor_queue_redraw (self->actor);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FADE_EDGES]);
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+st_scroll_view_fade_set_extend_fade_area (StScrollViewFade *self,
+ gboolean extend_fade_area)
+{
+ if (self->extend_fade_area == extend_fade_area)
+ return;
+
+ self->extend_fade_area = extend_fade_area;
+
+ if (self->actor != NULL)
+ clutter_actor_queue_redraw (self->actor);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EXTEND_FADE_AREA]);
+}
+
+static void
+st_scroll_view_fade_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (object);
+
+ switch (prop_id)
+ {
+ case PROP_FADE_MARGINS:
+ st_scroll_view_set_fade_margins (self, g_value_get_boxed (value));
+ break;
+ case PROP_FADE_EDGES:
+ st_scroll_view_fade_set_fade_edges (self, g_value_get_boolean (value));
+ break;
+ case PROP_EXTEND_FADE_AREA:
+ st_scroll_view_fade_set_extend_fade_area (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_view_fade_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (object);
+
+ switch (prop_id)
+ {
+ case PROP_FADE_MARGINS:
+ g_value_set_boxed (value, &self->fade_margins);
+ break;
+ case PROP_FADE_EDGES:
+ g_value_set_boolean (value, self->fade_edges);
+ break;
+ case PROP_EXTEND_FADE_AREA:
+ g_value_set_boolean (value, self->extend_fade_area);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_view_fade_class_init (StScrollViewFadeClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterShaderEffectClass *shader_class;
+ ClutterOffscreenEffectClass *offscreen_class;
+ ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
+
+ gobject_class->dispose = st_scroll_view_fade_dispose;
+ gobject_class->get_property = st_scroll_view_fade_get_property;
+ gobject_class->set_property = st_scroll_view_fade_set_property;
+
+ meta_class->set_actor = st_scroll_view_fade_set_actor;
+
+ shader_class = CLUTTER_SHADER_EFFECT_CLASS (klass);
+ shader_class->get_static_shader_source = st_scroll_view_fade_get_static_shader_source;
+
+ offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
+ offscreen_class->create_texture = st_scroll_view_fade_create_texture;
+ offscreen_class->paint_target = st_scroll_view_fade_paint_target;
+
+ /**
+ * StScrollViewFade:fade-margins:
+ *
+ * The margins widths that are faded.
+ */
+ props[PROP_FADE_MARGINS] =
+ g_param_spec_boxed ("fade-margins",
+ "Fade margins",
+ "The margin widths that are faded",
+ CLUTTER_TYPE_MARGIN,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollViewFade:fade-edges:
+ *
+ * Whether the faded area should extend to the edges of the #StScrollViewFade.
+ */
+ props[PROP_FADE_EDGES] =
+ g_param_spec_boolean ("fade-edges",
+ "Fade Edges",
+ "Whether the faded area should extend to the edges",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollViewFade:extend-fade-area:
+ *
+ * Whether faded edges should extend beyond the faded area of the #StScrollViewFade.
+ */
+ props[PROP_EXTEND_FADE_AREA] =
+ g_param_spec_boolean ("extend-fade-area",
+ "Extend Fade Area",
+ "Whether faded edges should extend beyond the faded area",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+st_scroll_view_fade_init (StScrollViewFade *self)
+{
+ self->fade_margins = (ClutterMargin) {
+ DEFAULT_FADE_OFFSET,
+ DEFAULT_FADE_OFFSET,
+ DEFAULT_FADE_OFFSET,
+ DEFAULT_FADE_OFFSET,
+ };
+}
+
+/**
+ * st_scroll_view_fade_new:
+ *
+ * Create a new #StScrollViewFade.
+ *
+ * Returns: (transfer full): a new #StScrollViewFade
+ */
+ClutterEffect *
+st_scroll_view_fade_new (void)
+{
+ return g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL);
+}
diff --git a/src/st/st-scroll-view-fade.glsl b/src/st/st-scroll-view-fade.glsl
new file mode 100644
index 0000000..ba6582f
--- /dev/null
+++ b/src/st/st-scroll-view-fade.glsl
@@ -0,0 +1,77 @@
+/*
+ * st-scroll-view-fade.glsl: Edge fade effect for StScrollView
+ *
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Adel Gadllah
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+uniform sampler2D tex;
+uniform float height;
+uniform float width;
+uniform float fade_offset_top;
+uniform float fade_offset_bottom;
+uniform float fade_offset_left;
+uniform float fade_offset_right;
+uniform bool fade_edges_top;
+uniform bool fade_edges_right;
+uniform bool fade_edges_bottom;
+uniform bool fade_edges_left;
+uniform bool extend_fade_area;
+
+uniform vec2 fade_area_topleft;
+uniform vec2 fade_area_bottomright;
+
+void main ()
+{
+ cogl_color_out = cogl_color_in * texture2D (tex, vec2 (cogl_tex_coord_in[0].xy));
+
+ float y = height * cogl_tex_coord_in[0].y;
+ float x = width * cogl_tex_coord_in[0].x;
+ float ratio = 1.0;
+
+ if (x > fade_area_topleft[0] && x < fade_area_bottomright[0] &&
+ y > fade_area_topleft[1] && y < fade_area_bottomright[1])
+ {
+ float after_left = x - fade_area_topleft[0];
+ float before_right = fade_area_bottomright[0] - x;
+ float after_top = y - fade_area_topleft[1];
+ float before_bottom = fade_area_bottomright[1] - y;
+
+ if (after_top < fade_offset_top && fade_edges_top) {
+ ratio *= after_top / fade_offset_top;
+ }
+
+ if (before_bottom < fade_offset_bottom && fade_edges_bottom) {
+ ratio *= before_bottom / fade_offset_bottom;
+ }
+
+ if (after_left < fade_offset_left && fade_edges_left) {
+ ratio *= after_left / fade_offset_left;
+ }
+
+ if (before_right < fade_offset_right && fade_edges_right) {
+ ratio *= before_right / fade_offset_right;
+ }
+ } else if (extend_fade_area) {
+ if (x <= fade_area_topleft[0] && fade_edges_left ||
+ x >= fade_area_bottomright[0] && fade_edges_right ||
+ y <= fade_area_topleft[1] && fade_edges_top ||
+ y >= fade_area_bottomright[1] && fade_edges_bottom) {
+ ratio = 0.0;
+ }
+ }
+
+ cogl_color_out *= ratio;
+}
diff --git a/src/st/st-scroll-view-fade.h b/src/st/st-scroll-view-fade.h
new file mode 100644
index 0000000..2c65a77
--- /dev/null
+++ b/src/st/st-scroll-view-fade.h
@@ -0,0 +1,36 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view-fade.h: Edge fade effect for StScrollView
+ *
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Adel Gadllah
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_SCROLL_VIEW_FADE_H__
+#define __ST_SCROLL_VIEW_FADE_H__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLL_VIEW_FADE (st_scroll_view_fade_get_type ())
+G_DECLARE_FINAL_TYPE (StScrollViewFade, st_scroll_view_fade,
+ ST, SCROLL_VIEW_FADE, ClutterShaderEffect)
+
+ClutterEffect *st_scroll_view_fade_new (void);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLL_VIEW_FADE_H__ */
diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c
new file mode 100644
index 0000000..50de481
--- /dev/null
+++ b/src/st/st-scroll-view.c
@@ -0,0 +1,1327 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view.h: Container with scroll-bars
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-scroll-view
+ * @short_description: a container for scrollable children
+ *
+ * #StScrollView is a single child container for actors that implement
+ * #StScrollable. It provides scrollbars around the edge of the child to
+ * allow the user to move around the scrollable area.
+ */
+
+/* TODO: The code here currently only deals with height-for-width
+ * allocation; width-for-height allocation would need a second set of
+ * code paths through get_preferred_height()/get_preferred_width()/allocate()
+ * that reverse the roles of the horizontal and vertical scrollbars.
+ *
+ * TODO: The multiple layout passes with and without scrollbars when
+ * using the automatic policy causes considerable inefficiency because
+ * it breaks request caching; we should saved the last size passed
+ * into allocate() and if it's the same as previous size not repeat
+ * the determination of scrollbar visibility. This requires overriding
+ * queue_relayout() so we know when to discard the saved value.
+ *
+ * The size negotiation between the #StScrollView and the child is
+ * described in the documentation for #StScrollable; the significant
+ * part to note there is that reported minimum sizes for a scrolled
+ * child are the minimum sizes when no scrollbar is needed. This allows
+ * us to determine what scrollbars are visible without a need to look
+ * inside the #StAdjustment.
+ *
+ * The second simplification that we make that allows us to implement
+ * a straightforward height-for-width negotiation without multiple
+ * allocate passes is that when the scrollbar policy is
+ * AUTO, we always reserve space for the scrollbar in the
+ * reported minimum and natural size.
+ *
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=611740 for a more
+ * detailed description of the considerations involved.
+ */
+
+#include "st-enum-types.h"
+#include "st-private.h"
+#include "st-scroll-view.h"
+#include "st-scroll-bar.h"
+#include "st-scrollable.h"
+#include "st-scroll-view-fade.h"
+#include <clutter/clutter.h>
+#include <math.h>
+
+static void clutter_container_iface_init (ClutterContainerIface *iface);
+
+static ClutterContainerIface *st_scroll_view_parent_iface = NULL;
+
+struct _StScrollViewPrivate
+{
+ /* a pointer to the child; this is actually stored
+ * inside StBin:child, but we keep it to avoid
+ * calling st_bin_get_child() every time we need it
+ */
+ ClutterActor *child;
+
+ StAdjustment *hadjustment;
+ ClutterActor *hscroll;
+ StAdjustment *vadjustment;
+ ClutterActor *vscroll;
+
+ StPolicyType hscrollbar_policy;
+ StPolicyType vscrollbar_policy;
+
+ gfloat row_size;
+ gfloat column_size;
+
+ StScrollViewFade *fade_effect;
+
+ guint row_size_set : 1;
+ guint column_size_set : 1;
+ guint mouse_scroll : 1;
+ guint overlay_scrollbars : 1;
+ guint hscrollbar_visible : 1;
+ guint vscrollbar_visible : 1;
+};
+
+G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN,
+ G_ADD_PRIVATE (StScrollView)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
+ clutter_container_iface_init))
+
+enum {
+ PROP_0,
+
+ PROP_HSCROLL,
+ PROP_VSCROLL,
+ PROP_HSCROLLBAR_POLICY,
+ PROP_VSCROLLBAR_POLICY,
+ PROP_HSCROLLBAR_VISIBLE,
+ PROP_VSCROLLBAR_VISIBLE,
+ PROP_MOUSE_SCROLL,
+ PROP_OVERLAY_SCROLLBARS,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static void
+st_scroll_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollViewPrivate *priv = ((StScrollView *) object)->priv;
+
+ switch (property_id)
+ {
+ case PROP_HSCROLL:
+ g_value_set_object (value, priv->hscroll);
+ break;
+ case PROP_VSCROLL:
+ g_value_set_object (value, priv->vscroll);
+ break;
+ case PROP_HSCROLLBAR_POLICY:
+ g_value_set_enum (value, priv->hscrollbar_policy);
+ break;
+ case PROP_VSCROLLBAR_POLICY:
+ g_value_set_enum (value, priv->vscrollbar_policy);
+ break;
+ case PROP_HSCROLLBAR_VISIBLE:
+ g_value_set_boolean (value, priv->hscrollbar_visible);
+ break;
+ case PROP_VSCROLLBAR_VISIBLE:
+ g_value_set_boolean (value, priv->vscrollbar_visible);
+ break;
+ case PROP_MOUSE_SCROLL:
+ g_value_set_boolean (value, priv->mouse_scroll);
+ break;
+ case PROP_OVERLAY_SCROLLBARS:
+ g_value_set_boolean (value, priv->overlay_scrollbars);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+/**
+ * st_scroll_view_update_fade_effect:
+ * @scroll: a #StScrollView
+ * @fade_margins: a #ClutterMargin defining the vertical fade effects, in pixels.
+ *
+ * Sets the fade effects in all four edges of the view. A value of 0
+ * disables the effect.
+ */
+void
+st_scroll_view_update_fade_effect (StScrollView *scroll,
+ ClutterMargin *fade_margins)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ /* A fade amount of other than 0 enables the effect. */
+ if (fade_margins->left != 0. || fade_margins->right != 0. ||
+ fade_margins->top != 0. || fade_margins->bottom != 0.)
+ {
+ if (priv->fade_effect == NULL)
+ {
+ priv->fade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL);
+
+ clutter_actor_add_effect_with_name (CLUTTER_ACTOR (scroll), "fade",
+ CLUTTER_EFFECT (priv->fade_effect));
+ }
+
+ g_object_set (priv->fade_effect,
+ "fade-margins", fade_margins,
+ NULL);
+ }
+ else
+ {
+ if (priv->fade_effect != NULL)
+ {
+ clutter_actor_remove_effect (CLUTTER_ACTOR (scroll),
+ CLUTTER_EFFECT (priv->fade_effect));
+ priv->fade_effect = NULL;
+ }
+ }
+}
+
+static void
+st_scroll_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollView *self = ST_SCROLL_VIEW (object);
+ StScrollViewPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_MOUSE_SCROLL:
+ st_scroll_view_set_mouse_scrolling (self,
+ g_value_get_boolean (value));
+ break;
+ case PROP_OVERLAY_SCROLLBARS:
+ st_scroll_view_set_overlay_scrollbars (self,
+ g_value_get_boolean (value));
+ break;
+ case PROP_HSCROLLBAR_POLICY:
+ st_scroll_view_set_policy (self,
+ g_value_get_enum (value),
+ priv->vscrollbar_policy);
+ break;
+ case PROP_VSCROLLBAR_POLICY:
+ st_scroll_view_set_policy (self,
+ priv->hscrollbar_policy,
+ g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_scroll_view_dispose (GObject *object)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv;
+
+ if (priv->fade_effect)
+ {
+ clutter_actor_remove_effect (CLUTTER_ACTOR (object), CLUTTER_EFFECT (priv->fade_effect));
+ priv->fade_effect = NULL;
+ }
+
+ g_clear_pointer (&priv->vscroll, clutter_actor_destroy);
+ g_clear_pointer (&priv->hscroll, clutter_actor_destroy);
+
+ /* For most reliable freeing of memory, an object with signals
+ * like StAdjustment should be explicitly disposed. Since we own
+ * the adjustments, we take care of that. This also disconnects
+ * the signal handlers that we established on creation.
+ */
+ if (priv->hadjustment)
+ {
+ g_object_run_dispose (G_OBJECT (priv->hadjustment));
+ g_object_unref (priv->hadjustment);
+ priv->hadjustment = NULL;
+ }
+
+ if (priv->vadjustment)
+ {
+ g_object_run_dispose (G_OBJECT (priv->vadjustment));
+ g_object_unref (priv->vadjustment);
+ priv->vadjustment = NULL;
+ }
+
+ G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object);
+}
+
+static void
+st_scroll_view_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->child)
+ clutter_actor_paint (priv->child, paint_context);
+ if (priv->hscrollbar_visible)
+ clutter_actor_paint (priv->hscroll, paint_context);
+ if (priv->vscrollbar_visible)
+ clutter_actor_paint (priv->vscroll, paint_context);
+}
+
+static void
+st_scroll_view_pick (ClutterActor *actor,
+ ClutterPickContext *pick_context)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+
+ /* Chain up so we get a bounding box pained (if we are reactive) */
+ CLUTTER_ACTOR_CLASS (st_scroll_view_parent_class)->pick (actor, pick_context);
+
+ if (priv->child)
+ clutter_actor_pick (priv->child, pick_context);
+ if (priv->hscrollbar_visible)
+ clutter_actor_pick (priv->hscroll, pick_context);
+ if (priv->vscrollbar_visible)
+ clutter_actor_pick (priv->vscroll, pick_context);
+}
+
+static gboolean
+st_scroll_view_get_paint_volume (ClutterActor *actor,
+ ClutterPaintVolume *volume)
+{
+ return clutter_paint_volume_set_from_allocation (volume, actor);
+}
+
+static double
+get_scrollbar_width (StScrollView *scroll,
+ gfloat for_height)
+{
+ StScrollViewPrivate *priv = scroll->priv;
+
+ if (clutter_actor_is_visible (priv->vscroll))
+ {
+ gfloat min_size;
+
+ clutter_actor_get_preferred_width (CLUTTER_ACTOR (priv->vscroll), for_height,
+ &min_size, NULL);
+ return min_size;
+ }
+ else
+ return 0;
+}
+
+static double
+get_scrollbar_height (StScrollView *scroll,
+ gfloat for_width)
+{
+ StScrollViewPrivate *priv = scroll->priv;
+
+ if (clutter_actor_is_visible (priv->hscroll))
+ {
+ gfloat min_size;
+
+ clutter_actor_get_preferred_height (CLUTTER_ACTOR (priv->hscroll), for_width,
+ &min_size, NULL);
+
+ return min_size;
+ }
+ else
+ return 0;
+}
+
+static void
+st_scroll_view_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gboolean account_for_vscrollbar = FALSE;
+ gfloat min_width = 0, natural_width;
+ gfloat child_min_width, child_natural_width;
+
+ if (!priv->child)
+ return;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ clutter_actor_get_preferred_width (priv->child, -1,
+ &child_min_width, &child_natural_width);
+
+ natural_width = child_natural_width;
+
+ switch (priv->hscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ min_width = child_min_width;
+ break;
+ case ST_POLICY_ALWAYS:
+ case ST_POLICY_AUTOMATIC:
+ case ST_POLICY_EXTERNAL:
+ /* Should theoretically use the min width of the hscrollbar,
+ * but that's not cleanly defined at the moment */
+ min_width = 0;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ switch (priv->vscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ case ST_POLICY_EXTERNAL:
+ account_for_vscrollbar = FALSE;
+ break;
+ case ST_POLICY_ALWAYS:
+ account_for_vscrollbar = !priv->overlay_scrollbars;
+ break;
+ case ST_POLICY_AUTOMATIC:
+ /* For automatic scrollbars, we always request space for the vertical
+ * scrollbar; we won't know whether we actually need one until our
+ * height is assigned in allocate().
+ */
+ account_for_vscrollbar = !priv->overlay_scrollbars;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ if (account_for_vscrollbar)
+ {
+ float sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), for_height);
+
+ min_width += sb_width;
+ natural_width += sb_width;
+ }
+
+ if (min_width_p)
+ *min_width_p = min_width;
+
+ if (natural_width_p)
+ *natural_width_p = natural_width;
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_scroll_view_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gboolean account_for_hscrollbar = FALSE;
+ gfloat min_height = 0, natural_height;
+ gfloat child_min_height, child_natural_height;
+ gfloat child_min_width;
+ gfloat sb_width;
+
+ if (!priv->child)
+ return;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ clutter_actor_get_preferred_width (priv->child, -1,
+ &child_min_width, NULL);
+
+ if (min_height_p)
+ *min_height_p = 0;
+
+ sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1);
+
+ switch (priv->vscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ case ST_POLICY_EXTERNAL:
+ break;
+ case ST_POLICY_ALWAYS:
+ case ST_POLICY_AUTOMATIC:
+ /* We've requested space for the scrollbar, subtract it back out */
+ for_width -= sb_width;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ switch (priv->hscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ case ST_POLICY_EXTERNAL:
+ account_for_hscrollbar = FALSE;
+ break;
+ case ST_POLICY_ALWAYS:
+ account_for_hscrollbar = !priv->overlay_scrollbars;
+ break;
+ case ST_POLICY_AUTOMATIC:
+ /* For automatic scrollbars, we always request space for the horizontal
+ * scrollbar; we won't know whether we actually need one until our
+ * width is assigned in allocate().
+ */
+ account_for_hscrollbar = !priv->overlay_scrollbars;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ clutter_actor_get_preferred_height (priv->child, for_width,
+ &child_min_height, &child_natural_height);
+
+ natural_height = child_natural_height;
+
+ switch (priv->vscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ min_height = child_min_height;
+ break;
+ case ST_POLICY_ALWAYS:
+ case ST_POLICY_AUTOMATIC:
+ case ST_POLICY_EXTERNAL:
+ /* Should theoretically use the min height of the vscrollbar,
+ * but that's not cleanly defined at the moment */
+ min_height = 0;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ if (account_for_hscrollbar)
+ {
+ float sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), for_width);
+
+ min_height += sb_height;
+ natural_height += sb_height;
+ }
+
+ if (min_height_p)
+ *min_height_p = min_height;
+
+ if (natural_height_p)
+ *natural_height_p = natural_height;
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_scroll_view_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ ClutterActorBox content_box, child_box;
+ gfloat avail_width, avail_height, sb_width, sb_height;
+ gboolean hscrollbar_visible, vscrollbar_visible;
+
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ avail_width = content_box.x2 - content_box.x1;
+ avail_height = content_box.y2 - content_box.y1;
+
+ if (clutter_actor_get_request_mode (actor) == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1);
+ sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), sb_width);
+ }
+ else
+ {
+ sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), -1);
+ sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), sb_height);
+ }
+
+ /* Determine what scrollbars are visible. The basic idea of the
+ * handling of an automatic scrollbars is that we start off with the
+ * assumption that we don't need any scrollbars, see if that works,
+ * and if not add horizontal and vertical scrollbars until we are no
+ * longer overflowing.
+ */
+ if (priv->child)
+ {
+ gfloat child_min_width;
+ gfloat child_min_height;
+
+ clutter_actor_get_preferred_width (priv->child, -1,
+ &child_min_width, NULL);
+
+ if (priv->vscrollbar_policy == ST_POLICY_AUTOMATIC)
+ {
+ if (priv->hscrollbar_policy == ST_POLICY_AUTOMATIC)
+ {
+ /* Pass one, try without a vertical scrollbar */
+ clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL);
+ vscrollbar_visible = child_min_height > avail_height;
+ hscrollbar_visible = child_min_width > avail_width - (vscrollbar_visible ? sb_width : 0);
+ vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0);
+
+ /* Pass two - if we needed a vertical scrollbar, get a new preferred height */
+ if (vscrollbar_visible)
+ {
+ clutter_actor_get_preferred_height (priv->child, MAX (avail_width - sb_width, 0),
+ &child_min_height, NULL);
+ hscrollbar_visible = child_min_width > avail_width - sb_width;
+ }
+ }
+ else
+ {
+ hscrollbar_visible = priv->hscrollbar_policy == ST_POLICY_ALWAYS;
+
+ /* try without a vertical scrollbar */
+ clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL);
+ vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0);
+ }
+ }
+ else
+ {
+ vscrollbar_visible = priv->vscrollbar_policy == ST_POLICY_ALWAYS;
+
+ if (priv->hscrollbar_policy == ST_POLICY_AUTOMATIC)
+ hscrollbar_visible = child_min_width > avail_height - (vscrollbar_visible ? 0 : sb_width);
+ else
+ hscrollbar_visible = priv->hscrollbar_policy == ST_POLICY_ALWAYS;
+ }
+ }
+ else
+ {
+ hscrollbar_visible = priv->hscrollbar_policy != ST_POLICY_NEVER &&
+ priv->hscrollbar_policy != ST_POLICY_EXTERNAL;
+ vscrollbar_visible = priv->vscrollbar_policy != ST_POLICY_NEVER &&
+ priv->vscrollbar_policy != ST_POLICY_EXTERNAL;
+ }
+
+ /* Whether or not we show the scrollbars, if the scrollbars are visible
+ * actors, we need to give them some allocation, so we unconditionally
+ * give them the "right" allocation; that might overlap the child when
+ * the scrollbars are not visible, but it doesn't matter because we
+ * don't include them in pick or paint.
+ */
+
+ /* Vertical scrollbar */
+ if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x1 + sb_width;
+ }
+ else
+ {
+ child_box.x1 = content_box.x2 - sb_width;
+ child_box.x2 = content_box.x2;
+ }
+ child_box.y1 = content_box.y1;
+ child_box.y2 = content_box.y2 - (hscrollbar_visible ? sb_height : 0);
+
+ clutter_actor_allocate (priv->vscroll, &child_box);
+
+ /* Horizontal scrollbar */
+ if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ child_box.x1 = content_box.x1 + (vscrollbar_visible ? sb_width : 0);
+ child_box.x2 = content_box.x2;
+ }
+ else
+ {
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x2 - (vscrollbar_visible ? sb_width : 0);
+ }
+ child_box.y1 = content_box.y2 - sb_height;
+ child_box.y2 = content_box.y2;
+
+ clutter_actor_allocate (priv->hscroll, &child_box);
+
+ /* In case the scrollbar policy is NEVER or EXTERNAL or scrollbars
+ * should be overlaid, we don't trim the content box allocation by
+ * the scrollbar size.
+ * Fold this into the scrollbar sizes to simplify the rest of the
+ * computations.
+ */
+ if (priv->hscrollbar_policy == ST_POLICY_NEVER ||
+ priv->hscrollbar_policy == ST_POLICY_EXTERNAL ||
+ priv->overlay_scrollbars)
+ sb_height = 0;
+ if (priv->vscrollbar_policy == ST_POLICY_NEVER ||
+ priv->vscrollbar_policy == ST_POLICY_EXTERNAL ||
+ priv->overlay_scrollbars)
+ sb_width = 0;
+
+ /* Child */
+ if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ child_box.x1 = content_box.x1 + sb_width;
+ child_box.x2 = content_box.x2;
+ }
+ else
+ {
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x2 - sb_width;
+ }
+ child_box.y1 = content_box.y1;
+ child_box.y2 = content_box.y2 - sb_height;
+
+ if (priv->child)
+ clutter_actor_allocate (priv->child, &child_box);
+
+ if (priv->hscrollbar_visible != hscrollbar_visible)
+ {
+ g_object_freeze_notify (G_OBJECT (actor));
+ priv->hscrollbar_visible = hscrollbar_visible;
+ g_object_notify_by_pspec (G_OBJECT (actor),
+ props[PROP_HSCROLLBAR_VISIBLE]);
+ g_object_thaw_notify (G_OBJECT (actor));
+ }
+
+ if (priv->vscrollbar_visible != vscrollbar_visible)
+ {
+ g_object_freeze_notify (G_OBJECT (actor));
+ priv->vscrollbar_visible = vscrollbar_visible;
+ g_object_notify_by_pspec (G_OBJECT (actor),
+ props[PROP_VSCROLLBAR_VISIBLE]);
+ g_object_thaw_notify (G_OBJECT (actor));
+ }
+
+}
+
+static void
+adjust_with_direction (StAdjustment *adj,
+ ClutterScrollDirection direction)
+{
+ gdouble delta;
+
+ switch (direction)
+ {
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_LEFT:
+ delta = -1.0;
+ break;
+ case CLUTTER_SCROLL_RIGHT:
+ case CLUTTER_SCROLL_DOWN:
+ delta = 1.0;
+ break;
+ case CLUTTER_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ st_adjustment_adjust_for_scroll_event (adj, delta);
+}
+
+static void
+st_scroll_view_style_changed (StWidget *widget)
+{
+ StScrollView *self = ST_SCROLL_VIEW (widget);
+ gboolean has_vfade, has_hfade;
+ double vfade_offset = 0.0;
+ double hfade_offset = 0.0;
+
+ StThemeNode *theme_node = st_widget_get_theme_node (widget);
+
+ has_vfade = st_theme_node_lookup_length (theme_node, "-st-vfade-offset", FALSE, &vfade_offset);
+ has_hfade = st_theme_node_lookup_length (theme_node, "-st-hfade-offset", FALSE, &hfade_offset);
+ if (has_vfade || has_hfade)
+ {
+ st_scroll_view_update_fade_effect (self,
+ &(ClutterMargin) {
+ .top = vfade_offset,
+ .bottom = vfade_offset,
+ .left = hfade_offset,
+ .right = hfade_offset,
+ });
+ }
+
+ ST_WIDGET_CLASS (st_scroll_view_parent_class)->style_changed (widget);
+}
+
+static gboolean
+st_scroll_view_scroll_event (ClutterActor *self,
+ ClutterScrollEvent *event)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv;
+ ClutterTextDirection direction;
+
+ /* don't handle scroll events if requested not to */
+ if (!priv->mouse_scroll)
+ return FALSE;
+
+ if (clutter_event_is_pointer_emulated ((ClutterEvent *) event))
+ return TRUE;
+
+ direction = clutter_actor_get_text_direction (self);
+
+ switch (event->direction)
+ {
+ case CLUTTER_SCROLL_SMOOTH:
+ {
+ gdouble delta_x, delta_y;
+ clutter_event_get_scroll_delta ((ClutterEvent *)event, &delta_x, &delta_y);
+
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ delta_x *= -1;
+
+ st_adjustment_adjust_for_scroll_event (priv->hadjustment, delta_x);
+ st_adjustment_adjust_for_scroll_event (priv->vadjustment, delta_y);
+ }
+ break;
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_DOWN:
+ adjust_with_direction (priv->vadjustment, event->direction);
+ break;
+ case CLUTTER_SCROLL_LEFT:
+ case CLUTTER_SCROLL_RIGHT:
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ ClutterScrollDirection dir;
+
+ dir = event->direction == CLUTTER_SCROLL_LEFT ? CLUTTER_SCROLL_RIGHT
+ : CLUTTER_SCROLL_LEFT;
+ adjust_with_direction (priv->hadjustment, dir);
+ }
+ else
+ {
+ adjust_with_direction (priv->hadjustment, event->direction);
+ }
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+st_scroll_view_class_init (StScrollViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ object_class->get_property = st_scroll_view_get_property;
+ object_class->set_property = st_scroll_view_set_property;
+ object_class->dispose = st_scroll_view_dispose;
+
+ actor_class->paint = st_scroll_view_paint;
+ actor_class->pick = st_scroll_view_pick;
+ actor_class->get_paint_volume = st_scroll_view_get_paint_volume;
+ actor_class->get_preferred_width = st_scroll_view_get_preferred_width;
+ actor_class->get_preferred_height = st_scroll_view_get_preferred_height;
+ actor_class->allocate = st_scroll_view_allocate;
+ actor_class->scroll_event = st_scroll_view_scroll_event;
+
+ widget_class->style_changed = st_scroll_view_style_changed;
+
+ /**
+ * StScrollView:hscroll:
+ *
+ * The horizontal #StScrollBar for the #StScrollView.
+ */
+ props[PROP_HSCROLL] =
+ g_param_spec_object ("hscroll",
+ "StScrollBar",
+ "Horizontal scroll indicator",
+ ST_TYPE_SCROLL_BAR,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:vscroll:
+ *
+ * The vertical #StScrollBar for the #StScrollView.
+ */
+ props[PROP_VSCROLL] =
+ g_param_spec_object ("vscroll",
+ "StScrollBar",
+ "Vertical scroll indicator",
+ ST_TYPE_SCROLL_BAR,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:vscrollbar-policy:
+ *
+ * The #StPolicyType for when to show the vertical #StScrollBar.
+ */
+ props[PROP_VSCROLLBAR_POLICY] =
+ g_param_spec_enum ("vscrollbar-policy",
+ "Vertical Scrollbar Policy",
+ "When the vertical scrollbar is displayed",
+ ST_TYPE_POLICY_TYPE,
+ ST_POLICY_AUTOMATIC,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollView:hscrollbar-policy:
+ *
+ * The #StPolicyType for when to show the horizontal #StScrollBar.
+ */
+ props[PROP_HSCROLLBAR_POLICY] =
+ g_param_spec_enum ("hscrollbar-policy",
+ "Horizontal Scrollbar Policy",
+ "When the horizontal scrollbar is displayed",
+ ST_TYPE_POLICY_TYPE,
+ ST_POLICY_AUTOMATIC,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollView:hscrollbar-visible:
+ *
+ * Whether the horizontal #StScrollBar is visible.
+ */
+ props[PROP_HSCROLLBAR_VISIBLE] =
+ g_param_spec_boolean ("hscrollbar-visible",
+ "Horizontal Scrollbar Visibility",
+ "Whether the horizontal scrollbar is visible",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:vscrollbar-visible:
+ *
+ * Whether the vertical #StScrollBar is visible.
+ */
+ props[PROP_VSCROLLBAR_VISIBLE] =
+ g_param_spec_boolean ("vscrollbar-visible",
+ "Vertical Scrollbar Visibility",
+ "Whether the vertical scrollbar is visible",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:enable-mouse-scrolling:
+ *
+ * Whether to enable automatic mouse wheel scrolling.
+ */
+ props[PROP_MOUSE_SCROLL] =
+ g_param_spec_boolean ("enable-mouse-scrolling",
+ "Enable Mouse Scrolling",
+ "Enable automatic mouse wheel scrolling",
+ TRUE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollView:overlay-scrollbars:
+ *
+ * Whether scrollbars are painted on top of the content.
+ */
+ props[PROP_OVERLAY_SCROLLBARS] =
+ g_param_spec_boolean ("overlay-scrollbars",
+ "Use Overlay Scrollbars",
+ "Overlay scrollbars over the content",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_scroll_view_init (StScrollView *self)
+{
+ StScrollViewPrivate *priv = self->priv = st_scroll_view_get_instance_private (self);
+
+ priv->hscrollbar_policy = ST_POLICY_AUTOMATIC;
+ priv->vscrollbar_policy = ST_POLICY_AUTOMATIC;
+
+ priv->hadjustment = g_object_new (ST_TYPE_ADJUSTMENT,
+ "actor", self,
+ NULL);
+ priv->hscroll = g_object_new (ST_TYPE_SCROLL_BAR,
+ "adjustment", priv->hadjustment,
+ "vertical", FALSE,
+ NULL);
+
+ priv->vadjustment = g_object_new (ST_TYPE_ADJUSTMENT,
+ "actor", self,
+ NULL);
+ priv->vscroll = g_object_new (ST_TYPE_SCROLL_BAR,
+ "adjustment", priv->vadjustment,
+ "vertical", TRUE,
+ NULL);
+
+ clutter_actor_add_child (CLUTTER_ACTOR (self), priv->hscroll);
+ clutter_actor_add_child (CLUTTER_ACTOR (self), priv->vscroll);
+
+ /* mouse scroll is enabled by default, so we also need to be reactive */
+ priv->mouse_scroll = TRUE;
+ g_object_set (G_OBJECT (self), "reactive", TRUE, NULL);
+}
+
+static void
+st_scroll_view_add (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StScrollView *self = ST_SCROLL_VIEW (container);
+ StScrollViewPrivate *priv = self->priv;
+
+ if (ST_IS_SCROLLABLE (actor))
+ {
+ priv->child = actor;
+
+ /* chain up to StBin::add() */
+ st_scroll_view_parent_iface->add (container, actor);
+
+ st_scrollable_set_adjustments (ST_SCROLLABLE (actor),
+ priv->hadjustment, priv->vadjustment);
+ }
+ else
+ {
+ g_warning ("Attempting to add an actor of type %s to "
+ "a StScrollView, but the actor does "
+ "not implement StScrollable.",
+ g_type_name (G_OBJECT_TYPE (actor)));
+ }
+}
+
+static void
+st_scroll_view_remove (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StScrollView *self = ST_SCROLL_VIEW (container);
+ StScrollViewPrivate *priv = self->priv;
+
+ if (actor == priv->child)
+ {
+ g_object_ref (priv->child);
+
+ /* chain up to StBin::remove() */
+ st_scroll_view_parent_iface->remove (container, actor);
+
+ st_scrollable_set_adjustments (ST_SCROLLABLE (priv->child),
+ NULL, NULL);
+
+ g_object_unref (priv->child);
+ priv->child = NULL;
+ }
+ else
+ {
+ if (actor == priv->vscroll)
+ priv->vscroll = NULL;
+ else if (actor == priv->hscroll)
+ priv->hscroll = NULL;
+ else
+ g_assert ("Unknown child removed from StScrollView");
+
+ clutter_actor_remove_child (CLUTTER_ACTOR (container), actor);
+ }
+}
+
+static void
+clutter_container_iface_init (ClutterContainerIface *iface)
+{
+ /* store a pointer to the StBin implementation of
+ * ClutterContainer so that we can chain up when
+ * overriding the methods
+ */
+ st_scroll_view_parent_iface = g_type_interface_peek_parent (iface);
+
+ iface->add = st_scroll_view_add;
+ iface->remove = st_scroll_view_remove;
+}
+
+/**
+ * st_scroll_view_new:
+ *
+ * Create a new #StScrollView.
+ *
+ * Returns: (transfer full): a new #StScrollView
+ */
+StWidget *
+st_scroll_view_new (void)
+{
+ return g_object_new (ST_TYPE_SCROLL_VIEW, NULL);
+}
+
+/**
+ * st_scroll_view_get_hscroll_bar:
+ * @scroll: a #StScrollView
+ *
+ * Gets the horizontal #StScrollBar of the #StScrollView.
+ *
+ * Returns: (transfer none): the horizontal scrollbar
+ */
+ClutterActor *
+st_scroll_view_get_hscroll_bar (StScrollView *scroll)
+{
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL);
+
+ return scroll->priv->hscroll;
+}
+
+/**
+ * st_scroll_view_get_vscroll_bar:
+ * @scroll: a #StScrollView
+ *
+ * Gets the vertical scrollbar of the #StScrollView.
+ *
+ * Returns: (transfer none): the vertical #StScrollBar
+ */
+ClutterActor *
+st_scroll_view_get_vscroll_bar (StScrollView *scroll)
+{
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL);
+
+ return scroll->priv->vscroll;
+}
+
+/**
+ * st_scroll_view_get_column_size:
+ * @scroll: a #StScrollView
+ *
+ * Get the step increment of the horizontal plane.
+ *
+ * Returns: the horizontal step increment
+ */
+gfloat
+st_scroll_view_get_column_size (StScrollView *scroll)
+{
+ gdouble column_size;
+
+ g_return_val_if_fail (scroll, 0);
+
+ g_object_get (scroll->priv->hadjustment,
+ "step-increment", &column_size,
+ NULL);
+
+ return column_size;
+}
+
+/**
+ * st_scroll_view_set_column_size:
+ * @scroll: a #StScrollView
+ * @column_size: horizontal step increment
+ *
+ * Set the step increment of the horizontal plane to @column_size.
+ */
+void
+st_scroll_view_set_column_size (StScrollView *scroll,
+ gfloat column_size)
+{
+ g_return_if_fail (scroll);
+
+ if (column_size < 0)
+ {
+ scroll->priv->column_size_set = FALSE;
+ scroll->priv->column_size = -1;
+ }
+ else
+ {
+ scroll->priv->column_size_set = TRUE;
+ scroll->priv->column_size = column_size;
+
+ g_object_set (scroll->priv->hadjustment,
+ "step-increment", (gdouble) scroll->priv->column_size,
+ NULL);
+ }
+}
+
+/**
+ * st_scroll_view_get_row_size:
+ * @scroll: a #StScrollView
+ *
+ * Get the step increment of the vertical plane.
+ *
+ * Returns: the vertical step increment
+ */
+gfloat
+st_scroll_view_get_row_size (StScrollView *scroll)
+{
+ gdouble row_size;
+
+ g_return_val_if_fail (scroll, 0);
+
+ g_object_get (scroll->priv->vadjustment,
+ "step-increment", &row_size,
+ NULL);
+
+ return row_size;
+}
+
+/**
+ * st_scroll_view_set_row_size:
+ * @scroll: a #StScrollView
+ * @row_size: vertical step increment
+ *
+ * Set the step increment of the vertical plane to @row_size.
+ */
+void
+st_scroll_view_set_row_size (StScrollView *scroll,
+ gfloat row_size)
+{
+ g_return_if_fail (scroll);
+
+ if (row_size < 0)
+ {
+ scroll->priv->row_size_set = FALSE;
+ scroll->priv->row_size = -1;
+ }
+ else
+ {
+ scroll->priv->row_size_set = TRUE;
+ scroll->priv->row_size = row_size;
+
+ g_object_set (scroll->priv->vadjustment,
+ "step-increment", (gdouble) scroll->priv->row_size,
+ NULL);
+ }
+}
+
+/**
+ * st_scroll_view_set_mouse_scrolling:
+ * @scroll: a #StScrollView
+ * @enabled: %TRUE or %FALSE
+ *
+ * Sets automatic mouse wheel scrolling to enabled or disabled.
+ */
+void
+st_scroll_view_set_mouse_scrolling (StScrollView *scroll,
+ gboolean enabled)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ if (priv->mouse_scroll != enabled)
+ {
+ priv->mouse_scroll = enabled;
+
+ /* make sure we can receive mouse wheel events */
+ if (enabled)
+ clutter_actor_set_reactive ((ClutterActor *) scroll, TRUE);
+
+ g_object_notify_by_pspec (G_OBJECT (scroll), props[PROP_MOUSE_SCROLL]);
+ }
+}
+
+/**
+ * st_scroll_view_get_mouse_scrolling:
+ * @scroll: a #StScrollView
+ *
+ * Get whether automatic mouse wheel scrolling is enabled or disabled.
+ *
+ * Returns: %TRUE if enabled, %FALSE otherwise
+ */
+gboolean
+st_scroll_view_get_mouse_scrolling (StScrollView *scroll)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ return priv->mouse_scroll;
+}
+
+/**
+ * st_scroll_view_set_overlay_scrollbars:
+ * @scroll: A #StScrollView
+ * @enabled: Whether to enable overlay scrollbars
+ *
+ * Sets whether scrollbars are painted on top of the content.
+ */
+void
+st_scroll_view_set_overlay_scrollbars (StScrollView *scroll,
+ gboolean enabled)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ if (priv->overlay_scrollbars != enabled)
+ {
+ priv->overlay_scrollbars = enabled;
+ g_object_notify_by_pspec (G_OBJECT (scroll),
+ props[PROP_OVERLAY_SCROLLBARS]);
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll));
+ }
+}
+
+/**
+ * st_scroll_view_get_overlay_scrollbars:
+ * @scroll: A #StScrollView
+ *
+ * Gets whether scrollbars are painted on top of the content.
+ *
+ * Returns: %TRUE if enabled, %FALSE otherwise
+ */
+gboolean
+st_scroll_view_get_overlay_scrollbars (StScrollView *scroll)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ return priv->overlay_scrollbars;
+}
+
+/**
+ * st_scroll_view_set_policy:
+ * @scroll: A #StScrollView
+ * @hscroll: Whether to enable horizontal scrolling
+ * @vscroll: Whether to enable vertical scrolling
+ *
+ * Set the scroll policy.
+ */
+void
+st_scroll_view_set_policy (StScrollView *scroll,
+ StPolicyType hscroll,
+ StPolicyType vscroll)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ if (priv->hscrollbar_policy == hscroll && priv->vscrollbar_policy == vscroll)
+ return;
+
+ g_object_freeze_notify ((GObject *) scroll);
+
+ if (priv->hscrollbar_policy != hscroll)
+ {
+ priv->hscrollbar_policy = hscroll;
+ g_object_notify_by_pspec ((GObject *) scroll,
+ props[PROP_HSCROLLBAR_POLICY]);
+ }
+
+ if (priv->vscrollbar_policy != vscroll)
+ {
+ priv->vscrollbar_policy = vscroll;
+ g_object_notify_by_pspec ((GObject *) scroll,
+ props[PROP_VSCROLLBAR_POLICY]);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll));
+
+ g_object_thaw_notify ((GObject *) scroll);
+}
diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h
new file mode 100644
index 0000000..e2acaca
--- /dev/null
+++ b/src/st/st-scroll-view.h
@@ -0,0 +1,90 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view.h: Container with scroll-bars
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ * Copyright 2010 Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SCROLL_VIEW_H__
+#define __ST_SCROLL_VIEW_H__
+
+#include <st/st-bin.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLL_VIEW (st_scroll_view_get_type())
+G_DECLARE_FINAL_TYPE (StScrollView, st_scroll_view, ST, SCROLL_VIEW, StBin)
+
+typedef enum
+{
+ ST_POLICY_ALWAYS,
+ ST_POLICY_AUTOMATIC,
+ ST_POLICY_NEVER,
+ ST_POLICY_EXTERNAL,
+} StPolicyType;
+
+typedef struct _StScrollViewPrivate StScrollViewPrivate;
+
+/**
+ * StScrollView:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StScrollView
+{
+ /*< private >*/
+ StBin parent_instance;
+
+ StScrollViewPrivate *priv;
+};
+
+StWidget *st_scroll_view_new (void);
+
+ClutterActor *st_scroll_view_get_hscroll_bar (StScrollView *scroll);
+ClutterActor *st_scroll_view_get_vscroll_bar (StScrollView *scroll);
+
+gfloat st_scroll_view_get_column_size (StScrollView *scroll);
+void st_scroll_view_set_column_size (StScrollView *scroll,
+ gfloat column_size);
+
+gfloat st_scroll_view_get_row_size (StScrollView *scroll);
+void st_scroll_view_set_row_size (StScrollView *scroll,
+ gfloat row_size);
+
+void st_scroll_view_set_mouse_scrolling (StScrollView *scroll,
+ gboolean enabled);
+gboolean st_scroll_view_get_mouse_scrolling (StScrollView *scroll);
+
+void st_scroll_view_set_overlay_scrollbars (StScrollView *scroll,
+ gboolean enabled);
+gboolean st_scroll_view_get_overlay_scrollbars (StScrollView *scroll);
+
+void st_scroll_view_set_policy (StScrollView *scroll,
+ StPolicyType hscroll,
+ StPolicyType vscroll);
+void st_scroll_view_update_fade_effect (StScrollView *scroll,
+ ClutterMargin *fade_margins);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLL_VIEW_H__ */
diff --git a/src/st/st-scrollable.c b/src/st/st-scrollable.c
new file mode 100644
index 0000000..3a77052
--- /dev/null
+++ b/src/st/st-scrollable.c
@@ -0,0 +1,196 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scrollable.c: Scrollable interface
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-private.h"
+#include "st-scrollable.h"
+
+G_DEFINE_INTERFACE (StScrollable, st_scrollable, G_TYPE_OBJECT)
+
+/**
+ * SECTION:st-scrollable
+ * @short_description: A #ClutterActor that can be scrolled
+ *
+ * The #StScrollable interface is exposed by actors that support scrolling.
+ *
+ * The interface contains methods for getting and setting the adjustments
+ * for scrolling; these adjustments will be used to hook the scrolled
+ * position up to scrollbars or other external controls. When a #StScrollable
+ * is added to a parent container, the parent container is responsible
+ * for setting the adjustments. The parent container then sets the adjustments
+ * back to %NULL when the scrollable is removed.
+ *
+ * For #StScrollable supporting height-for-width size negotiation, size
+ * negotiation works as follows:
+ *
+ * In response to get_preferred_width(), the scrollable should report
+ * the minimum width at which horizontal scrolling is needed for the
+ * preferred width, and natural width of the actor when not
+ * horizontally scrolled as the natural width.
+ *
+ * The for_width passed into get_preferred_height() is the width at which
+ * the scrollable will be allocated; this will be smaller than the minimum
+ * width when scrolling horizontally, so the scrollable may want to adjust
+ * it up to the minimum width before computing a preferred height. (Other
+ * scrollables may want to fit as much content into the allocated area
+ * as possible and only scroll what absolutely needs to scroll - consider,
+ * for example, the line-wrapping behavior of a text editor where there
+ * is a long line without any spaces.) As for width, get_preferred_height()
+ * should return the minimum size at which no scrolling is needed for the
+ * minimum height, and the natural size of the actor when not vertically scrolled
+ * as the natural height.
+ *
+ * In allocate() the allocation box passed in will be actual allocated
+ * size of the actor so will be smaller than the reported minimum
+ * width and/or height when scrolling is present. Any scrollable actor
+ * must support being allocated at any size down to 0x0 without
+ * crashing, however if the actor has content around the scrolled area
+ * and has an absolute minimum size that's bigger than 0x0 its
+ * acceptable for it to misdraw between 0x0 and the absolute minimum
+ * size. It's up to the application author to avoid letting the user
+ * resize the scroll view small enough so that the scrolled area
+ * vanishes.
+ *
+ * In response to allocate, in addition to normal handling, the
+ * scrollable should also set the limits of the the horizontal and
+ * vertical adjustments that were set on it earlier. The standard
+ * settings are:
+ *
+ * lower: 0
+ * page_size: allocated size (width or height)
+ * upper: MAX (total size of the scrolled area,allocated_size)
+ * step_increment: natural row/column height or a fixed fraction of the page size
+ * page_increment: page_size - step_increment
+ */
+static void
+st_scrollable_default_init (StScrollableInterface *g_iface)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ /**
+ * StScrollable:hadjustment:
+ *
+ * The horizontal #StAdjustment used by the #StScrollable.
+ *
+ * Implementations should override this property to provide read-write
+ * access to the #StAdjustment.
+ *
+ * JavaScript code may override this as demonstrated below:
+ *
+ * |[<!-- language="JavaScript" -->
+ * var MyScrollable = GObject.registerClass({
+ * Properties: {
+ * 'hadjustment': GObject.ParamSpec.override(
+ * 'hadjustment',
+ * St.Scrollable
+ * )
+ * }
+ * }, class MyScrollable extends St.Scrollable {
+ *
+ * get hadjustment() {
+ * return this._hadjustment || null;
+ * }
+ *
+ * set hadjustment(adjustment) {
+ * if (this.hadjustment === adjustment)
+ * return;
+ *
+ * this._hadjustment = adjustment;
+ * this.notify('hadjustment');
+ * }
+ * });
+ * ]|
+ */
+ g_object_interface_install_property (g_iface,
+ g_param_spec_object ("hadjustment",
+ "StAdjustment",
+ "Horizontal adjustment",
+ ST_TYPE_ADJUSTMENT,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY));
+
+ /**
+ * StScrollable:vadjustment:
+ *
+ * The vertical #StAdjustment used by the #StScrollable.
+ *
+ * Implementations should override this property to provide read-write
+ * access to the #StAdjustment.
+ *
+ * See #StScrollable:hadjustment for an example of how to override this
+ * property in JavaScript code.
+ */
+ g_object_interface_install_property (g_iface,
+ g_param_spec_object ("vadjustment",
+ "StAdjustment",
+ "Vertical adjustment",
+ ST_TYPE_ADJUSTMENT,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY));
+
+ initialized = TRUE;
+ }
+}
+
+/**
+ * st_scrollable_set_adjustments:
+ * @scrollable: a #StScrollable
+ * @hadjustment: the horizontal #StAdjustment
+ * @vadjustment: the vertical #StAdjustment
+ *
+ * This method should be implemented by classes implementing the #StScrollable
+ * interface.
+ *
+ * JavaScript code should do this by overriding the `vfunc_set_adjustments()`
+ * method.
+ */
+void
+st_scrollable_set_adjustments (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment)
+{
+ ST_SCROLLABLE_GET_IFACE (scrollable)->set_adjustments (scrollable,
+ hadjustment,
+ vadjustment);
+}
+
+/**
+ * st_scroll_bar_get_adjustments:
+ * @hadjustment: (transfer none) (out) (optional): location to store the horizontal adjustment, or %NULL
+ * @vadjustment: (transfer none) (out) (optional): location to store the vertical adjustment, or %NULL
+ *
+ * Gets the adjustment objects that store the offsets of the scrollable widget
+ * into its possible scrolling area.
+ *
+ * This method should be implemented by classes implementing the #StScrollable
+ * interface.
+ *
+ * JavaScript code should do this by overriding the `vfunc_get_adjustments()`
+ * method.
+ */
+void
+st_scrollable_get_adjustments (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment)
+{
+ ST_SCROLLABLE_GET_IFACE (scrollable)->get_adjustments (scrollable,
+ hadjustment,
+ vadjustment);
+}
diff --git a/src/st/st-scrollable.h b/src/st/st-scrollable.h
new file mode 100644
index 0000000..797ec7d
--- /dev/null
+++ b/src/st/st-scrollable.h
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scrollable.h: Scrollable interface
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SCROLLABLE_H__
+#define __ST_SCROLLABLE_H__
+
+#include <glib-object.h>
+#include <st/st-adjustment.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLLABLE (st_scrollable_get_type ())
+G_DECLARE_INTERFACE (StScrollable, st_scrollable, ST, SCROLLABLE, GObject)
+
+typedef struct _StScrollableInterface StScrollableInterface;
+
+struct _StScrollableInterface
+{
+ GTypeInterface parent;
+
+ void (* set_adjustments) (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment);
+ void (* get_adjustments) (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment);
+};
+
+void st_scrollable_set_adjustments (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment);
+void st_scrollable_get_adjustments (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLLABLE_H__ */
diff --git a/src/st/st-settings.c b/src/st/st-settings.c
new file mode 100644
index 0000000..04bf68f
--- /dev/null
+++ b/src/st/st-settings.c
@@ -0,0 +1,451 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-settings.c: Global settings
+ *
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <gio/gio.h>
+
+#include "st-private.h"
+#include "st-settings.h"
+
+#define KEY_ENABLE_ANIMATIONS "enable-animations"
+#define KEY_PRIMARY_PASTE "gtk-enable-primary-paste"
+#define KEY_DRAG_THRESHOLD "drag-threshold"
+#define KEY_FONT_NAME "font-name"
+#define KEY_HIGH_CONTRAST "high-contrast"
+#define KEY_GTK_ICON_THEME "icon-theme"
+#define KEY_MAGNIFIER_ACTIVE "screen-magnifier-enabled"
+#define KEY_DISABLE_SHOW_PASSWORD "disable-show-password"
+
+enum {
+ PROP_0,
+ PROP_ENABLE_ANIMATIONS,
+ PROP_PRIMARY_PASTE,
+ PROP_DRAG_THRESHOLD,
+ PROP_FONT_NAME,
+ PROP_HIGH_CONTRAST,
+ PROP_GTK_ICON_THEME,
+ PROP_MAGNIFIER_ACTIVE,
+ PROP_SLOW_DOWN_FACTOR,
+ PROP_DISABLE_SHOW_PASSWORD,
+ N_PROPS
+};
+
+GParamSpec *props[N_PROPS] = { 0 };
+
+struct _StSettings
+{
+ GObject parent_object;
+ GSettings *interface_settings;
+ GSettings *mouse_settings;
+ GSettings *a11y_applications_settings;
+ GSettings *a11y_interface_settings;
+ GSettings *lockdown_settings;
+
+ gchar *font_name;
+ gboolean high_contrast;
+ gchar *gtk_icon_theme;
+ int inhibit_animations_count;
+ gboolean enable_animations;
+ gboolean primary_paste;
+ gboolean magnifier_active;
+ gboolean disable_show_password;
+ gint drag_threshold;
+ double slow_down_factor;
+};
+
+G_DEFINE_TYPE (StSettings, st_settings, G_TYPE_OBJECT)
+
+#define EPSILON (1e-10)
+
+static void
+st_settings_set_slow_down_factor (StSettings *settings,
+ double factor)
+{
+ if (fabs (settings->slow_down_factor - factor) < EPSILON)
+ return;
+
+ settings->slow_down_factor = factor;
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_SLOW_DOWN_FACTOR]);
+}
+
+static gboolean
+get_enable_animations (StSettings *settings)
+{
+ if (settings->inhibit_animations_count > 0)
+ return FALSE;
+ else
+ return settings->enable_animations;
+}
+
+void
+st_settings_inhibit_animations (StSettings *settings)
+{
+ gboolean enable_animations;
+
+ enable_animations = get_enable_animations (settings);
+ settings->inhibit_animations_count++;
+
+ if (enable_animations != get_enable_animations (settings))
+ g_object_notify_by_pspec (G_OBJECT (settings),
+ props[PROP_ENABLE_ANIMATIONS]);
+}
+
+void
+st_settings_uninhibit_animations (StSettings *settings)
+{
+ gboolean enable_animations;
+
+ enable_animations = get_enable_animations (settings);
+ settings->inhibit_animations_count--;
+
+ if (enable_animations != get_enable_animations (settings))
+ g_object_notify_by_pspec (G_OBJECT (settings),
+ props[PROP_ENABLE_ANIMATIONS]);
+}
+
+static void
+st_settings_finalize (GObject *object)
+{
+ StSettings *settings = ST_SETTINGS (object);
+
+ g_object_unref (settings->interface_settings);
+ g_object_unref (settings->mouse_settings);
+ g_object_unref (settings->a11y_applications_settings);
+ g_object_unref (settings->a11y_interface_settings);
+ g_object_unref (settings->lockdown_settings);
+ g_free (settings->font_name);
+ g_free (settings->gtk_icon_theme);
+
+ G_OBJECT_CLASS (st_settings_parent_class)->finalize (object);
+}
+
+static void
+st_settings_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StSettings *settings = ST_SETTINGS (object);
+
+ switch (prop_id)
+ {
+ case PROP_SLOW_DOWN_FACTOR:
+ st_settings_set_slow_down_factor (settings, g_value_get_double (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+st_settings_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StSettings *settings = ST_SETTINGS (object);
+
+ switch (prop_id)
+ {
+ case PROP_ENABLE_ANIMATIONS:
+ g_value_set_boolean (value, get_enable_animations (settings));
+ break;
+ case PROP_PRIMARY_PASTE:
+ g_value_set_boolean (value, settings->primary_paste);
+ break;
+ case PROP_DRAG_THRESHOLD:
+ g_value_set_int (value, settings->drag_threshold);
+ break;
+ case PROP_FONT_NAME:
+ g_value_set_string (value, settings->font_name);
+ break;
+ case PROP_HIGH_CONTRAST:
+ g_value_set_boolean (value, settings->high_contrast);
+ break;
+ case PROP_GTK_ICON_THEME:
+ g_value_set_string (value, settings->gtk_icon_theme);
+ break;
+ case PROP_MAGNIFIER_ACTIVE:
+ g_value_set_boolean (value, settings->magnifier_active);
+ break;
+ case PROP_SLOW_DOWN_FACTOR:
+ g_value_set_double (value, settings->slow_down_factor);
+ break;
+ case PROP_DISABLE_SHOW_PASSWORD:
+ g_value_set_boolean (value, settings->disable_show_password);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+st_settings_class_init (StSettingsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = st_settings_finalize;
+ object_class->set_property = st_settings_set_property;
+ object_class->get_property = st_settings_get_property;
+
+ /**
+ * StSettings:enable-animations:
+ *
+ * Whether animations are enabled.
+ */
+ props[PROP_ENABLE_ANIMATIONS] = g_param_spec_boolean ("enable-animations",
+ "Enable animations",
+ "Enable animations",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:primary-paste:
+ *
+ * Whether pasting from the `PRIMARY` selection is supported (eg. middle-click
+ * paste).
+ */
+ props[PROP_PRIMARY_PASTE] = g_param_spec_boolean ("primary-paste",
+ "Primary paste",
+ "Primary paste",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:drag-threshold:
+ *
+ * The threshold before a drag operation begins.
+ */
+ props[PROP_DRAG_THRESHOLD] = g_param_spec_int ("drag-threshold",
+ "Drag threshold",
+ "Drag threshold",
+ 0, G_MAXINT, 8,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:font-name:
+ *
+ * The current font name.
+ */
+ props[PROP_FONT_NAME] = g_param_spec_string ("font-name",
+ "font name",
+ "font name",
+ "",
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:high-contrast:
+ *
+ * Whether the accessibility high contrast mode is enabled.
+ */
+ props[PROP_HIGH_CONTRAST] = g_param_spec_boolean ("high-contrast",
+ "High contrast",
+ "High contrast",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:gtk-icon-theme:
+ *
+ * The current GTK icon theme
+ */
+ props[PROP_GTK_ICON_THEME] = g_param_spec_string ("gtk-icon-theme",
+ "GTK Icon Theme",
+ "GTK Icon Theme",
+ "",
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:magnifier-active:
+ *
+ * Whether the accessibility magnifier is active.
+ */
+ props[PROP_MAGNIFIER_ACTIVE] = g_param_spec_boolean("magnifier-active",
+ "Magnifier is active",
+ "Whether the a11y magnifier is active",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:slow-down-factor:
+ *
+ * The slow-down factor applied to all animation durations.
+ */
+ props[PROP_SLOW_DOWN_FACTOR] = g_param_spec_double("slow-down-factor",
+ "Slow down factor",
+ "Factor applied to all animation durations",
+ EPSILON, G_MAXDOUBLE, 1.0,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StSettings:disable-show-password:
+ *
+ * Whether password showing can be locked down
+ */
+ props[PROP_DISABLE_SHOW_PASSWORD] = g_param_spec_boolean("disable-show-password",
+ "'Show Password' is disabled",
+ "Whether user can request to see their password",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+on_interface_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_ENABLE_ANIMATIONS))
+ {
+ settings->enable_animations = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_ENABLE_ANIMATIONS]);
+ }
+ else if (g_str_equal (key, KEY_PRIMARY_PASTE))
+ {
+ settings->primary_paste = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_PRIMARY_PASTE]);
+ }
+ else if (g_str_equal (key, KEY_FONT_NAME))
+ {
+ g_free (settings->font_name);
+ settings->font_name = g_settings_get_string (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_FONT_NAME]);
+ }
+ else if (g_str_equal (key, KEY_GTK_ICON_THEME))
+ {
+ g_free (settings->gtk_icon_theme);
+ settings->gtk_icon_theme = g_settings_get_string (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings),
+ props[PROP_GTK_ICON_THEME]);
+ }
+}
+
+static void
+on_mouse_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_DRAG_THRESHOLD))
+ {
+ settings->drag_threshold = g_settings_get_int (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_DRAG_THRESHOLD]);
+ }
+}
+
+static void
+on_a11y_applications_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_MAGNIFIER_ACTIVE))
+ {
+ settings->magnifier_active = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_MAGNIFIER_ACTIVE]);
+ }
+}
+
+static void
+on_a11y_interface_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_HIGH_CONTRAST))
+ {
+ settings->high_contrast = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_HIGH_CONTRAST]);
+
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_GTK_ICON_THEME]);
+ }
+}
+
+static void
+on_lockdown_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_DISABLE_SHOW_PASSWORD))
+ {
+ settings->disable_show_password = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_DISABLE_SHOW_PASSWORD]);
+ }
+}
+
+static void
+st_settings_init (StSettings *settings)
+{
+ settings->interface_settings = g_settings_new ("org.gnome.desktop.interface");
+ g_signal_connect (settings->interface_settings, "changed",
+ G_CALLBACK (on_interface_settings_changed), settings);
+
+ settings->mouse_settings = g_settings_new ("org.gnome.desktop.peripherals.mouse");
+ g_signal_connect (settings->mouse_settings, "changed",
+ G_CALLBACK (on_mouse_settings_changed), settings);
+
+ settings->a11y_applications_settings = g_settings_new ("org.gnome.desktop.a11y.applications");
+ g_signal_connect (settings->a11y_applications_settings, "changed",
+ G_CALLBACK (on_a11y_applications_settings_changed), settings);
+
+ settings->a11y_interface_settings = g_settings_new ("org.gnome.desktop.a11y.interface");
+ g_signal_connect (settings->a11y_interface_settings, "changed",
+ G_CALLBACK (on_a11y_interface_settings_changed), settings);
+
+ settings->lockdown_settings = g_settings_new ("org.gnome.desktop.lockdown");
+ g_signal_connect (settings->lockdown_settings, "changed",
+ G_CALLBACK (on_lockdown_settings_changed), settings);
+
+ settings->enable_animations = g_settings_get_boolean (settings->interface_settings,
+ KEY_ENABLE_ANIMATIONS);
+ settings->primary_paste = g_settings_get_boolean (settings->interface_settings,
+ KEY_PRIMARY_PASTE);
+ settings->font_name = g_settings_get_string (settings->interface_settings,
+ KEY_FONT_NAME);
+ settings->gtk_icon_theme = g_settings_get_string (settings->interface_settings,
+ KEY_GTK_ICON_THEME);
+ settings->drag_threshold = g_settings_get_int (settings->mouse_settings,
+ KEY_DRAG_THRESHOLD);
+ settings->magnifier_active = g_settings_get_boolean (settings->a11y_applications_settings,
+ KEY_MAGNIFIER_ACTIVE);
+ settings->high_contrast = g_settings_get_boolean (settings->a11y_interface_settings,
+ KEY_HIGH_CONTRAST);
+ settings->slow_down_factor = 1.;
+ settings->disable_show_password = g_settings_get_boolean (settings->lockdown_settings, KEY_DISABLE_SHOW_PASSWORD);
+}
+
+/**
+ * st_settings_get:
+ *
+ * Gets the global #StSettings object.
+ *
+ * Returns: (transfer none): the global #StSettings object
+ **/
+StSettings *
+st_settings_get (void)
+{
+ static StSettings *settings = NULL;
+
+ if (!settings)
+ settings = g_object_new (ST_TYPE_SETTINGS, NULL);
+
+ return settings;
+}
diff --git a/src/st/st-settings.h b/src/st/st-settings.h
new file mode 100644
index 0000000..8b25494
--- /dev/null
+++ b/src/st/st-settings.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-settings.h: Global settings
+ *
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SETTINGS_H__
+#define __ST_SETTINGS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SETTINGS (st_settings_get_type ())
+G_DECLARE_FINAL_TYPE (StSettings, st_settings, ST, SETTINGS, GObject)
+
+StSettings * st_settings_get (void);
+
+void st_settings_inhibit_animations (StSettings *settings);
+
+void st_settings_uninhibit_animations (StSettings *settings);
+
+G_END_DECLS
+
+#endif /* __ST_SETTINGS_H__ */
diff --git a/src/st/st-shadow.c b/src/st/st-shadow.c
new file mode 100644
index 0000000..0a8e319
--- /dev/null
+++ b/src/st/st-shadow.c
@@ -0,0 +1,307 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-shadow.c: Boxed type holding for -st-shadow attributes
+ *
+ * Copyright 2009, 2010 Florian Müllner
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "st-shadow.h"
+#include "st-private.h"
+
+G_DEFINE_BOXED_TYPE (StShadow, st_shadow, st_shadow_ref, st_shadow_unref)
+G_DEFINE_BOXED_TYPE (StShadowHelper, st_shadow_helper, st_shadow_helper_copy, st_shadow_helper_free)
+
+/**
+ * SECTION: st-shadow
+ * @short_description: Boxed type for -st-shadow attributes
+ *
+ * #StShadow is a boxed type for storing attributes of the -st-shadow
+ * property, modelled liberally after the CSS3 box-shadow property.
+ * See http://www.css3.info/preview/box-shadow/
+ *
+ */
+
+/**
+ * st_shadow_new:
+ * @color: shadow's color
+ * @xoffset: horizontal offset
+ * @yoffset: vertical offset
+ * @blur: blur radius
+ * @spread: spread radius
+ * @inset: whether the shadow should be inset
+ *
+ * Creates a new #StShadow
+ *
+ * Returns: the newly allocated shadow. Use st_shadow_free() when done
+ */
+StShadow *
+st_shadow_new (ClutterColor *color,
+ gdouble xoffset,
+ gdouble yoffset,
+ gdouble blur,
+ gdouble spread,
+ gboolean inset)
+{
+ StShadow *shadow;
+
+ shadow = g_new (StShadow, 1);
+
+ shadow->color = *color;
+ shadow->xoffset = xoffset;
+ shadow->yoffset = yoffset;
+ shadow->blur = blur;
+ shadow->spread = spread;
+ shadow->inset = inset;
+ shadow->ref_count = 1;
+
+ return shadow;
+}
+
+/**
+ * st_shadow_ref:
+ * @shadow: a #StShadow
+ *
+ * Atomically increments the reference count of @shadow by one.
+ *
+ * Returns: the passed in #StShadow.
+ */
+StShadow *
+st_shadow_ref (StShadow *shadow)
+{
+ g_return_val_if_fail (shadow != NULL, NULL);
+ g_return_val_if_fail (shadow->ref_count > 0, shadow);
+
+ g_atomic_int_inc (&shadow->ref_count);
+ return shadow;
+}
+
+/**
+ * st_shadow_unref:
+ * @shadow: a #StShadow
+ *
+ * Atomically decrements the reference count of @shadow by one.
+ * If the reference count drops to 0, all memory allocated by the
+ * #StShadow is released.
+ */
+void
+st_shadow_unref (StShadow *shadow)
+{
+ g_return_if_fail (shadow != NULL);
+ g_return_if_fail (shadow->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&shadow->ref_count))
+ g_free (shadow);
+}
+
+/**
+ * st_shadow_equal:
+ * @shadow: a #StShadow
+ * @other: a different #StShadow
+ *
+ * Check if two shadow objects are identical. Note that two shadows may
+ * compare non-identically if they differ only by floating point rounding
+ * errors.
+ *
+ * Returns: %TRUE if the two shadows are identical
+ */
+gboolean
+st_shadow_equal (StShadow *shadow,
+ StShadow *other)
+{
+ g_return_val_if_fail (shadow != NULL, FALSE);
+ g_return_val_if_fail (other != NULL, FALSE);
+
+ if (shadow == other)
+ return TRUE;
+
+ /* We use strict equality to compare double quantities; this means
+ * that, for example, a shadow offset of 0.25in does not necessarily
+ * compare equal to a shadow offset of 18pt in this test. Assume
+ * that a few false negatives are mostly harmless.
+ */
+
+ return (clutter_color_equal (&shadow->color, &other->color) &&
+ shadow->xoffset == other->xoffset &&
+ shadow->yoffset == other->yoffset &&
+ shadow->blur == other->blur &&
+ shadow->spread == other->spread &&
+ shadow->inset == other->inset);
+}
+
+/**
+ * st_shadow_get_box:
+ * @shadow: a #StShadow
+ * @actor_box: the box allocated to a #ClutterAlctor
+ * @shadow_box: computed box occupied by @shadow
+ *
+ * Gets the box used to paint @shadow, which will be partly
+ * outside of @actor_box
+ */
+void
+st_shadow_get_box (StShadow *shadow,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *shadow_box)
+{
+ g_return_if_fail (shadow != NULL);
+ g_return_if_fail (actor_box != NULL);
+ g_return_if_fail (shadow_box != NULL);
+
+ /* Inset shadows are drawn below the border, so returning
+ * the original box is not actually correct; still, it's
+ * good enough for the purpose of determining additional space
+ * required outside the actor box.
+ */
+ if (shadow->inset)
+ {
+ *shadow_box = *actor_box;
+ return;
+ }
+
+ shadow_box->x1 = actor_box->x1 + shadow->xoffset
+ - shadow->blur - shadow->spread;
+ shadow_box->x2 = actor_box->x2 + shadow->xoffset
+ + shadow->blur + shadow->spread;
+ shadow_box->y1 = actor_box->y1 + shadow->yoffset
+ - shadow->blur - shadow->spread;
+ shadow_box->y2 = actor_box->y2 + shadow->yoffset
+ + shadow->blur + shadow->spread;
+}
+
+/**
+ * SECTION: st-shadow-helper
+ *
+ * An helper for implementing a drop shadow on a actor.
+ * The actor is expected to recreate the helper whenever its contents
+ * or size change. Then, it would call st_shadow_helper_paint() inside
+ * its paint() virtual function.
+ */
+
+struct _StShadowHelper {
+ StShadow *shadow;
+ CoglPipeline *pipeline;
+
+ gfloat width;
+ gfloat height;
+};
+
+/**
+ * st_shadow_helper_new:
+ * @shadow: a #StShadow representing the shadow properties
+ *
+ * Builds a #StShadowHelper that will build a drop shadow
+ * using @source as the mask.
+ *
+ * Returns: (transfer full): a new #StShadowHelper
+ */
+StShadowHelper *
+st_shadow_helper_new (StShadow *shadow)
+{
+ StShadowHelper *helper;
+
+ helper = g_new0 (StShadowHelper, 1);
+ helper->shadow = st_shadow_ref (shadow);
+
+ return helper;
+}
+
+/**
+ * st_shadow_helper_update:
+ * @helper: a #StShadowHelper
+ * @source: a #ClutterActor
+ *
+ * Update @helper from @source.
+ */
+void
+st_shadow_helper_update (StShadowHelper *helper,
+ ClutterActor *source)
+{
+ gfloat width, height;
+
+ clutter_actor_get_size (source, &width, &height);
+
+ if (helper->pipeline == NULL ||
+ helper->width != width ||
+ helper->height != height)
+ {
+ if (helper->pipeline)
+ cogl_object_unref (helper->pipeline);
+
+ helper->pipeline = _st_create_shadow_pipeline_from_actor (helper->shadow, source);
+ helper->width = width;
+ helper->height = height;
+ }
+}
+
+/**
+ * st_shadow_helper_copy:
+ * @helper: the #StShadowHelper to copy
+ *
+ * Returns: (transfer full): a copy of @helper
+ */
+StShadowHelper *
+st_shadow_helper_copy (StShadowHelper *helper)
+{
+ StShadowHelper *copy;
+
+ copy = g_new (StShadowHelper, 1);
+ *copy = *helper;
+ if (copy->pipeline)
+ cogl_object_ref (copy->pipeline);
+ st_shadow_ref (copy->shadow);
+
+ return copy;
+}
+
+/**
+ * st_shadow_helper_free:
+ * @helper: a #StShadowHelper
+ *
+ * Free resources associated with @helper.
+ */
+void
+st_shadow_helper_free (StShadowHelper *helper)
+{
+ if (helper->pipeline)
+ cogl_object_unref (helper->pipeline);
+ st_shadow_unref (helper->shadow);
+
+ g_free (helper);
+}
+
+/**
+ * st_shadow_helper_paint:
+ * @helper: a #StShadowHelper
+ * @framebuffer: a #CoglFramebuffer
+ * @actor_box: the bounding box of the shadow
+ * @paint_opacity: the opacity at which the shadow is painted
+ *
+ * Paints the shadow associated with @helper This must only
+ * be called from the implementation of ClutterActor::paint().
+ */
+void
+st_shadow_helper_paint (StShadowHelper *helper,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *actor_box,
+ guint8 paint_opacity)
+{
+ _st_paint_shadow_with_opacity (helper->shadow,
+ framebuffer,
+ helper->pipeline,
+ actor_box,
+ paint_opacity);
+}
diff --git a/src/st/st-shadow.h b/src/st/st-shadow.h
new file mode 100644
index 0000000..267d48f
--- /dev/null
+++ b/src/st/st-shadow.h
@@ -0,0 +1,95 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-shadow.h: Boxed type holding for -st-shadow attributes
+ *
+ * Copyright 2009, 2010 Florian Müllner
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_SHADOW__
+#define __ST_SHADOW__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SHADOW (st_shadow_get_type ())
+#define ST_TYPE_SHADOW_HELPER (st_shadow_get_type ())
+
+typedef struct _StShadow StShadow;
+typedef struct _StShadowHelper StShadowHelper;
+
+/**
+ * StShadow:
+ * @color: shadow's color
+ * @xoffset: horizontal offset - positive values mean placement to the right,
+ * negative values placement to the left of the element.
+ * @yoffset: vertical offset - positive values mean placement below, negative
+ * values placement above the element.
+ * @blur: shadow's blur radius - a value of 0.0 will result in a hard shadow.
+ * @spread: shadow's spread radius - grow the shadow without enlarging the
+ * blur.
+ *
+ * Attributes of the -st-shadow property.
+ */
+struct _StShadow {
+ ClutterColor color;
+ gdouble xoffset;
+ gdouble yoffset;
+ gdouble blur;
+ gdouble spread;
+ gboolean inset;
+ volatile int ref_count;
+};
+
+GType st_shadow_get_type (void) G_GNUC_CONST;
+
+StShadow *st_shadow_new (ClutterColor *color,
+ gdouble xoffset,
+ gdouble yoffset,
+ gdouble blur,
+ gdouble spread,
+ gboolean inset);
+StShadow *st_shadow_ref (StShadow *shadow);
+void st_shadow_unref (StShadow *shadow);
+
+gboolean st_shadow_equal (StShadow *shadow,
+ StShadow *other);
+
+void st_shadow_get_box (StShadow *shadow,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *shadow_box);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (StShadow, st_shadow_unref)
+
+
+GType st_shadow_helper_get_type (void) G_GNUC_CONST;
+
+StShadowHelper *st_shadow_helper_new (StShadow *shadow);
+
+StShadowHelper *st_shadow_helper_copy (StShadowHelper *helper);
+void st_shadow_helper_free (StShadowHelper *helper);
+
+void st_shadow_helper_update (StShadowHelper *helper,
+ ClutterActor *source);
+
+void st_shadow_helper_paint (StShadowHelper *helper,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *actor_box,
+ guint8 paint_opacity);
+
+G_END_DECLS
+
+#endif /* __ST_SHADOW__ */
diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c
new file mode 100644
index 0000000..7062221
--- /dev/null
+++ b/src/st/st-texture-cache.c
@@ -0,0 +1,1688 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-texture-cache.h: Object for loading and caching images as textures
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010, Maxim Ermilov
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "st-image-content.h"
+#include "st-texture-cache.h"
+#include "st-private.h"
+#include "st-settings.h"
+#include <gtk/gtk.h>
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+
+#define CACHE_PREFIX_ICON "icon:"
+#define CACHE_PREFIX_FILE "file:"
+#define CACHE_PREFIX_FILE_FOR_CAIRO "file-for-cairo:"
+
+struct _StTextureCachePrivate
+{
+ GtkIconTheme *icon_theme;
+ GSettings *settings;
+
+ /* Things that were loaded with a cache policy != NONE */
+ GHashTable *keyed_cache; /* char * -> ClutterImage* */
+ GHashTable *keyed_surface_cache; /* char * -> cairo_surface_t* */
+
+ GHashTable *used_scales; /* Set: double */
+
+ /* Presently this is used to de-duplicate requests for GIcons and async URIs. */
+ GHashTable *outstanding_requests; /* char * -> AsyncTextureLoadData * */
+
+ /* File monitors to evict cache data on changes */
+ GHashTable *file_monitors; /* char * -> GFileMonitor * */
+
+ GCancellable *cancellable;
+};
+
+static void st_texture_cache_dispose (GObject *object);
+static void st_texture_cache_finalize (GObject *object);
+
+enum
+{
+ ICON_THEME_CHANGED,
+ TEXTURE_FILE_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+G_DEFINE_TYPE(StTextureCache, st_texture_cache, G_TYPE_OBJECT);
+
+/* We want to preserve the aspect ratio by default, also the default
+ * pipeline for an empty texture is full opacity white, which we
+ * definitely don't want. Skip that by setting 0 opacity.
+ */
+static ClutterActor *
+create_invisible_actor (void)
+{
+ return g_object_new (CLUTTER_TYPE_ACTOR,
+ "opacity", 0,
+ "request-mode", CLUTTER_REQUEST_CONTENT_SIZE,
+ NULL);
+}
+
+/* Reverse the opacity we added while loading */
+static void
+set_content_from_image (ClutterActor *actor,
+ ClutterContent *image)
+{
+ g_assert (image && CLUTTER_IS_IMAGE (image));
+
+ clutter_actor_set_content (actor, image);
+ clutter_actor_set_opacity (actor, 255);
+}
+
+static void
+st_texture_cache_class_init (StTextureCacheClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+
+ gobject_class->dispose = st_texture_cache_dispose;
+ gobject_class->finalize = st_texture_cache_finalize;
+
+ /**
+ * StTextureCache::icon-theme-changed:
+ * @self: a #StTextureCache
+ *
+ * Emitted when the icon theme is changed.
+ */
+ signals[ICON_THEME_CHANGED] =
+ g_signal_new ("icon-theme-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StTextureCache::texture-file-changed:
+ * @self: a #StTextureCache
+ * @file: a #GFile
+ *
+ * Emitted when the source file of a texture is changed.
+ */
+ signals[TEXTURE_FILE_CHANGED] =
+ g_signal_new ("texture-file-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_FILE);
+}
+
+/* Evicts all cached textures for named icons */
+static void
+st_texture_cache_evict_icons (StTextureCache *cache)
+{
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, cache->priv->keyed_cache);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const char *cache_key = key;
+
+ /* This is too conservative - it takes out all cached textures
+ * for GIcons even when they aren't named icons, but it's not
+ * worth the complexity of parsing the key and calling
+ * g_icon_new_for_string(); icon theme changes aren't normal */
+ if (g_str_has_prefix (cache_key, CACHE_PREFIX_ICON))
+ g_hash_table_iter_remove (&iter);
+ }
+}
+
+static void
+on_icon_theme_changed (StSettings *settings,
+ GParamSpec *pspec,
+ StTextureCache *cache)
+{
+ g_autofree gchar *theme = NULL;
+
+ g_cancellable_cancel (cache->priv->cancellable);
+ g_cancellable_reset (cache->priv->cancellable);
+
+ st_texture_cache_evict_icons (cache);
+
+ g_object_get (settings, "gtk-icon-theme", &theme, NULL);
+ gtk_icon_theme_set_custom_theme (cache->priv->icon_theme, theme);
+
+ g_signal_emit (cache, signals[ICON_THEME_CHANGED], 0);
+}
+
+static void
+on_gtk_icon_theme_changed (GtkIconTheme *icon_theme,
+ StTextureCache *self)
+{
+ st_texture_cache_evict_icons (self);
+ g_signal_emit (self, signals[ICON_THEME_CHANGED], 0);
+}
+
+static void
+st_texture_cache_init (StTextureCache *self)
+{
+ StSettings *settings;
+
+ self->priv = g_new0 (StTextureCachePrivate, 1);
+
+ self->priv->icon_theme = gtk_icon_theme_new ();
+ gtk_icon_theme_add_resource_path (self->priv->icon_theme,
+ "/org/gnome/shell/icons");
+ g_signal_connect (self->priv->icon_theme, "changed",
+ G_CALLBACK (on_gtk_icon_theme_changed), self);
+
+ settings = st_settings_get ();
+ g_signal_connect (settings, "notify::gtk-icon-theme",
+ G_CALLBACK (on_icon_theme_changed), self);
+
+ self->priv->keyed_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ self->priv->keyed_surface_cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) cairo_surface_destroy);
+ self->priv->used_scales = g_hash_table_new_full (g_double_hash, g_double_equal,
+ g_free, NULL);
+ self->priv->outstanding_requests = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ self->priv->file_monitors = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ g_object_unref, g_object_unref);
+
+ self->priv->cancellable = g_cancellable_new ();
+
+ on_icon_theme_changed (settings, NULL, self);
+}
+
+static void
+st_texture_cache_dispose (GObject *object)
+{
+ StTextureCache *self = (StTextureCache*)object;
+
+ g_cancellable_cancel (self->priv->cancellable);
+
+ g_clear_object (&self->priv->settings);
+ g_clear_object (&self->priv->icon_theme);
+ g_clear_object (&self->priv->cancellable);
+
+ g_clear_pointer (&self->priv->keyed_cache, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->keyed_surface_cache, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->used_scales, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->outstanding_requests, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->file_monitors, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object);
+}
+
+static void
+st_texture_cache_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object);
+}
+
+static void
+compute_pixbuf_scale (gint width,
+ gint height,
+ gint available_width,
+ gint available_height,
+ gint *new_width,
+ gint *new_height)
+{
+ int scaled_width, scaled_height;
+
+ if (width == 0 || height == 0)
+ {
+ *new_width = *new_height = 0;
+ return;
+ }
+
+ if (available_width >= 0 && available_height >= 0)
+ {
+ /* This should keep the aspect ratio of the image intact, because if
+ * available_width < (available_height * width) / height
+ * then
+ * (available_width * height) / width < available_height
+ * So we are guaranteed to either scale the image to have an available_width
+ * for width and height scaled accordingly OR have the available_height
+ * for height and width scaled accordingly, whichever scaling results
+ * in the image that can fit both available dimensions.
+ */
+ scaled_width = MIN (available_width, (available_height * width) / height);
+ scaled_height = MIN (available_height, (available_width * height) / width);
+ }
+ else if (available_width >= 0)
+ {
+ scaled_width = available_width;
+ scaled_height = (available_width * height) / width;
+ }
+ else if (available_height >= 0)
+ {
+ scaled_width = (available_height * width) / height;
+ scaled_height = available_height;
+ }
+ else
+ {
+ scaled_width = scaled_height = 0;
+ }
+
+ /* Scale the image only if that will not increase its original dimensions. */
+ if (scaled_width > 0 && scaled_height > 0 && scaled_width < width && scaled_height < height)
+ {
+ *new_width = scaled_width;
+ *new_height = scaled_height;
+ }
+ else
+ {
+ *new_width = width;
+ *new_height = height;
+ }
+}
+
+static void
+rgba_from_clutter (GdkRGBA *rgba,
+ ClutterColor *color)
+{
+ rgba->red = color->red / 255.;
+ rgba->green = color->green / 255.;
+ rgba->blue = color->blue / 255.;
+ rgba->alpha = color->alpha / 255.;
+}
+
+/* A private structure for keeping width, height and scale. */
+typedef struct {
+ int width;
+ int height;
+ int scale;
+} Dimensions;
+
+/* This struct corresponds to a request for an texture.
+ * It's creasted when something needs a new texture,
+ * and destroyed when the texture data is loaded. */
+typedef struct {
+ StTextureCache *cache;
+ StTextureCachePolicy policy;
+ char *key;
+
+ guint width;
+ guint height;
+ guint paint_scale;
+ gfloat resource_scale;
+ GSList *actors;
+
+ GtkIconInfo *icon_info;
+ StIconColors *colors;
+ GFile *file;
+} AsyncTextureLoadData;
+
+static void
+texture_load_data_free (gpointer p)
+{
+ AsyncTextureLoadData *data = p;
+
+ if (data->icon_info)
+ {
+ g_object_unref (data->icon_info);
+ if (data->colors)
+ st_icon_colors_unref (data->colors);
+ }
+ else if (data->file)
+ g_object_unref (data->file);
+
+ if (data->key)
+ g_free (data->key);
+
+ if (data->actors)
+ g_slist_free_full (data->actors, (GDestroyNotify) g_object_unref);
+
+ g_free (data);
+}
+
+/**
+ * on_image_size_prepared:
+ * @pixbuf_loader: #GdkPixbufLoader loading the image
+ * @width: the original width of the image
+ * @height: the original height of the image
+ * @data: pointer to the #Dimensions structure containing available width and height for the image,
+ * available width or height can be -1 if the dimension is not limited
+ *
+ * Private function.
+ *
+ * Sets the size of the image being loaded to fit the available width and height dimensions,
+ * but never scales up the image beyond its actual size.
+ * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
+ */
+static void
+on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ Dimensions *available_dimensions = data;
+ int available_width = available_dimensions->width;
+ int available_height = available_dimensions->height;
+ int scale_factor = available_dimensions->scale;
+ int scaled_width;
+ int scaled_height;
+
+ compute_pixbuf_scale (width, height, available_width, available_height,
+ &scaled_width, &scaled_height);
+
+ gdk_pixbuf_loader_set_size (pixbuf_loader,
+ scaled_width * scale_factor,
+ scaled_height * scale_factor);
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_data (const guchar *data,
+ gsize size,
+ int available_width,
+ int available_height,
+ int scale,
+ GError **error)
+{
+ GdkPixbufLoader *pixbuf_loader = NULL;
+ GdkPixbuf *rotated_pixbuf = NULL;
+ GdkPixbuf *pixbuf;
+ gboolean success;
+ Dimensions available_dimensions;
+ int width_before_rotation, width_after_rotation;
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ available_dimensions.width = available_width;
+ available_dimensions.height = available_height;
+ available_dimensions.scale = scale;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
+ if (!success)
+ goto out;
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ width_before_rotation = gdk_pixbuf_get_width (pixbuf);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
+
+ /* There is currently no way to tell if the pixbuf will need to be rotated before it is loaded,
+ * so we only check that once it is loaded, and reload it again if it needs to be rotated in order
+ * to use the available width and height correctly.
+ * See http://bugzilla.gnome.org/show_bug.cgi?id=579003
+ */
+ if (width_before_rotation != width_after_rotation)
+ {
+ g_object_unref (pixbuf_loader);
+ g_object_unref (rotated_pixbuf);
+ rotated_pixbuf = NULL;
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ /* We know that the image will later be rotated, so we reverse the available dimensions. */
+ available_dimensions.width = available_height;
+ available_dimensions.height = available_width;
+ available_dimensions.scale = scale;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
+ if (!success)
+ goto out;
+
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ }
+
+out:
+ if (pixbuf_loader)
+ g_object_unref (pixbuf_loader);
+ return rotated_pixbuf;
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_file (GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ float resource_scale,
+ GError **error)
+{
+ GdkPixbuf *pixbuf = NULL;
+ char *contents = NULL;
+ gsize size;
+
+ if (g_file_load_contents (file, NULL, &contents, &size, NULL, error))
+ {
+ int scale = ceilf (paint_scale * resource_scale);
+ pixbuf = impl_load_pixbuf_data ((const guchar *) contents, size,
+ available_width, available_height,
+ scale,
+ error);
+ }
+
+ g_free (contents);
+
+ return pixbuf;
+}
+
+static void
+load_pixbuf_thread (GTask *result,
+ gpointer source,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GdkPixbuf *pixbuf;
+ AsyncTextureLoadData *data = task_data;
+ GError *error = NULL;
+
+ g_assert (data != NULL);
+ g_assert (data->file != NULL);
+
+ pixbuf = impl_load_pixbuf_file (data->file, data->width, data->height,
+ data->paint_scale, data->resource_scale,
+ &error);
+
+ if (error != NULL)
+ g_task_return_error (result, error);
+ else if (pixbuf)
+ g_task_return_pointer (result, g_object_ref (pixbuf), g_object_unref);
+
+ g_clear_object (&pixbuf);
+}
+
+static GdkPixbuf *
+load_pixbuf_async_finish (StTextureCache *cache, GAsyncResult *result, GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static ClutterContent *
+pixbuf_to_st_content_image (GdkPixbuf *pixbuf,
+ int width,
+ int height,
+ int paint_scale,
+ float resource_scale)
+{
+ ClutterContent *image;
+ g_autoptr(GError) error = NULL;
+
+ float native_width, native_height;
+
+ native_width = ceilf (gdk_pixbuf_get_width (pixbuf) / resource_scale);
+ native_height = ceilf (gdk_pixbuf_get_height (pixbuf) / resource_scale);
+
+ if (width < 0 && height < 0)
+ {
+ width = native_width;
+ height = native_height;
+ }
+ else if (width < 0)
+ {
+ height *= paint_scale;
+ width = native_width * (height / native_height);
+ }
+ else if (height < 0)
+ {
+ width *= paint_scale;
+ height = native_height * (width / native_width);
+ }
+ else
+ {
+ width *= paint_scale;
+ height *= paint_scale;
+ }
+
+ image = st_image_content_new_with_preferred_size (width, height);
+ clutter_image_set_data (CLUTTER_IMAGE (image),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_has_alpha (pixbuf) ?
+ COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ &error);
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_clear_object (&image);
+ }
+
+ return image;
+}
+
+static cairo_surface_t *
+pixbuf_to_cairo_surface (GdkPixbuf *pixbuf)
+{
+ cairo_surface_t *dummy_surface;
+ cairo_pattern_t *pattern;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ dummy_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+
+ cr = cairo_create (dummy_surface);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ pattern = cairo_get_source (cr);
+ cairo_pattern_get_surface (pattern, &surface);
+ cairo_surface_reference (surface);
+ cairo_destroy (cr);
+ cairo_surface_destroy (dummy_surface);
+
+ return surface;
+}
+
+static void
+finish_texture_load (AsyncTextureLoadData *data,
+ GdkPixbuf *pixbuf)
+{
+ g_autoptr(ClutterContent) image = NULL;
+ GSList *iter;
+ StTextureCache *cache;
+
+ cache = data->cache;
+
+ g_hash_table_remove (cache->priv->outstanding_requests, data->key);
+
+ if (pixbuf == NULL)
+ goto out;
+
+ if (data->policy != ST_TEXTURE_CACHE_POLICY_NONE)
+ {
+ gpointer orig_key = NULL, value = NULL;
+
+ if (!g_hash_table_lookup_extended (cache->priv->keyed_cache, data->key,
+ &orig_key, &value))
+ {
+ image = pixbuf_to_st_content_image (pixbuf,
+ data->width, data->height,
+ data->paint_scale,
+ data->resource_scale);
+ if (!image)
+ goto out;
+
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (data->key),
+ g_object_ref (image));
+ }
+ else
+ {
+ image = g_object_ref (value);
+ }
+ }
+ else
+ {
+ image = pixbuf_to_st_content_image (pixbuf,
+ data->width, data->height,
+ data->paint_scale,
+ data->resource_scale);
+ if (!image)
+ goto out;
+ }
+
+ for (iter = data->actors; iter; iter = iter->next)
+ {
+ ClutterActor *actor = iter->data;
+ set_content_from_image (actor, image);
+ }
+
+out:
+ texture_load_data_free (data);
+}
+
+static void
+on_symbolic_icon_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = gtk_icon_info_load_symbolic_finish (GTK_ICON_INFO (source), result, NULL, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+on_icon_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = gtk_icon_info_load_icon_finish (GTK_ICON_INFO (source), result, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+on_pixbuf_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = load_pixbuf_async_finish (ST_TEXTURE_CACHE (source), result, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+load_texture_async (StTextureCache *cache,
+ AsyncTextureLoadData *data)
+{
+ if (data->file)
+ {
+ GTask *task = g_task_new (cache, NULL, on_pixbuf_loaded, data);
+ g_task_set_task_data (task, data, NULL);
+ g_task_run_in_thread (task, load_pixbuf_thread);
+ g_object_unref (task);
+ }
+ else if (data->icon_info)
+ {
+ StIconColors *colors = data->colors;
+ if (colors)
+ {
+ GdkRGBA foreground_color;
+ GdkRGBA success_color;
+ GdkRGBA warning_color;
+ GdkRGBA error_color;
+
+ rgba_from_clutter (&foreground_color, &colors->foreground);
+ rgba_from_clutter (&success_color, &colors->success);
+ rgba_from_clutter (&warning_color, &colors->warning);
+ rgba_from_clutter (&error_color, &colors->error);
+
+ gtk_icon_info_load_symbolic_async (data->icon_info,
+ &foreground_color, &success_color,
+ &warning_color, &error_color,
+ cache->priv->cancellable,
+ on_symbolic_icon_loaded, data);
+ }
+ else
+ {
+ gtk_icon_info_load_icon_async (data->icon_info,
+ cache->priv->cancellable,
+ on_icon_loaded, data);
+ }
+ }
+ else
+ g_assert_not_reached ();
+}
+
+typedef struct {
+ StTextureCache *cache;
+ ClutterContent *image;
+ GObject *source;
+ gulong notify_signal_id;
+ gboolean weakref_active;
+} StTextureCachePropertyBind;
+
+static void
+st_texture_cache_load_surface (ClutterContent **image,
+ cairo_surface_t *surface)
+{
+ g_return_if_fail (image != NULL);
+
+ if (surface != NULL &&
+ cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE &&
+ (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32 ||
+ cairo_image_surface_get_format (surface) == CAIRO_FORMAT_RGB24))
+ {
+ g_autoptr(GError) error = NULL;
+ int width, height, size;
+
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_width (surface);
+ size = MAX(width, height);
+
+ if (*image == NULL)
+ *image = st_image_content_new_with_preferred_size (size, size);
+
+ clutter_image_set_data (CLUTTER_IMAGE (*image),
+ cairo_image_surface_get_data (surface),
+ cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32 ?
+ COGL_PIXEL_FORMAT_BGRA_8888 : COGL_PIXEL_FORMAT_BGR_888,
+ width,
+ height,
+ cairo_image_surface_get_stride (surface),
+ &error);
+
+ if (error)
+ g_warning ("Failed to allocate texture: %s", error->message);
+ }
+ else if (*image == NULL)
+ {
+ *image = st_image_content_new_with_preferred_size (0, 0);
+ }
+}
+
+static void
+st_texture_cache_reset_texture (StTextureCachePropertyBind *bind,
+ const char *propname)
+{
+ cairo_surface_t *surface;
+
+ g_object_get (bind->source, propname, &surface, NULL);
+
+ st_texture_cache_load_surface (&bind->image, surface);
+}
+
+static void
+st_texture_cache_on_pixbuf_notify (GObject *object,
+ GParamSpec *paramspec,
+ gpointer data)
+{
+ StTextureCachePropertyBind *bind = data;
+ st_texture_cache_reset_texture (bind, paramspec->name);
+}
+
+static void
+st_texture_cache_bind_weak_notify (gpointer data,
+ GObject *source_location)
+{
+ StTextureCachePropertyBind *bind = data;
+ bind->weakref_active = FALSE;
+ g_signal_handler_disconnect (bind->source, bind->notify_signal_id);
+}
+
+static void
+st_texture_cache_free_bind (gpointer data)
+{
+ StTextureCachePropertyBind *bind = data;
+ if (bind->weakref_active)
+ g_object_weak_unref (G_OBJECT (bind->image), st_texture_cache_bind_weak_notify, bind);
+ g_free (bind);
+}
+
+/**
+ * st_texture_cache_bind_cairo_surface_property:
+ * @cache: A #StTextureCache
+ * @object: A #GObject with a property @property_name of type #cairo_surface_t
+ * @property_name: Name of a property
+ *
+ * Create a #GIcon which tracks the #cairo_surface_t value of a GObject property
+ * named by @property_name. Unlike other methods in StTextureCache, the underlying
+ * #CoglTexture is not shared by default with other invocations to this method.
+ *
+ * If the source object is destroyed, the texture will continue to show the last
+ * value of the property.
+ *
+ * Returns: (transfer full): A new #GIcon
+ */
+GIcon *
+st_texture_cache_bind_cairo_surface_property (StTextureCache *cache,
+ GObject *object,
+ const char *property_name)
+{
+ gchar *notify_key;
+ StTextureCachePropertyBind *bind;
+
+ bind = g_new0 (StTextureCachePropertyBind, 1);
+ bind->cache = cache;
+ bind->source = object;
+
+ st_texture_cache_reset_texture (bind, property_name);
+
+ g_object_weak_ref (G_OBJECT (bind->image), st_texture_cache_bind_weak_notify, bind);
+ bind->weakref_active = TRUE;
+
+ notify_key = g_strdup_printf ("notify::%s", property_name);
+ bind->notify_signal_id = g_signal_connect_data (object, notify_key, G_CALLBACK(st_texture_cache_on_pixbuf_notify),
+ bind, (GClosureNotify)st_texture_cache_free_bind, 0);
+ g_free (notify_key);
+
+ return G_ICON (bind->image);
+}
+
+/**
+ * st_texture_cache_load_cairo_surface_to_gicon:
+ * @cache: A #StTextureCache
+ * @surface: A #cairo_surface_t
+ *
+ * Create a #GIcon from @surface.
+ *
+ * Returns: (transfer full): A new #GIcon
+ */
+GIcon *
+st_texture_cache_load_cairo_surface_to_gicon (StTextureCache *cache,
+ cairo_surface_t *surface)
+{
+ ClutterContent *image = NULL;
+ st_texture_cache_load_surface (&image, surface);
+
+ return G_ICON (image);
+}
+
+/**
+ * st_texture_cache_load: (skip)
+ * @cache: A #StTextureCache
+ * @key: Arbitrary string used to refer to item
+ * @policy: Caching policy
+ * @load: Function to create the texture, if not already cached
+ * @data: User data passed to @load
+ * @error: A #GError
+ *
+ * Load an arbitrary texture, caching it. The string chosen for @key
+ * should be of the form "type-prefix:type-uuid". For example,
+ * "url:file:///usr/share/icons/hicolor/48x48/apps/firefox.png", or
+ * "stock-icon:gtk-ok".
+ *
+ * Returns: (transfer full): A newly-referenced handle to the texture
+ */
+CoglTexture *
+st_texture_cache_load (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ StTextureCacheLoader load,
+ void *data,
+ GError **error)
+{
+ CoglTexture *texture;
+
+ texture = g_hash_table_lookup (cache->priv->keyed_cache, key);
+ if (!texture)
+ {
+ texture = load (cache, key, data, error);
+ if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), texture);
+ }
+
+ if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ cogl_object_ref (texture);
+
+ return texture;
+}
+
+/**
+ * ensure_request:
+ * @cache: A #StTextureCache
+ * @key: A cache key
+ * @policy: Cache policy
+ * @request: (out): If no request is outstanding, one will be created and returned here
+ * @texture: A texture to be added to the request
+ *
+ * Check for any outstanding load for the data represented by @key. If there
+ * is already a request pending, append it to that request to avoid loading
+ * the data multiple times.
+ *
+ * Returns: %TRUE if there is already a request pending
+ */
+static gboolean
+ensure_request (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ AsyncTextureLoadData **request,
+ ClutterActor *actor)
+{
+ ClutterContent *image;
+ AsyncTextureLoadData *pending;
+ gboolean had_pending;
+
+ image = g_hash_table_lookup (cache->priv->keyed_cache, key);
+
+ if (image != NULL)
+ {
+ /* We had this cached already, just set the texture and we're done. */
+ set_content_from_image (actor, image);
+ return TRUE;
+ }
+
+ pending = g_hash_table_lookup (cache->priv->outstanding_requests, key);
+ had_pending = pending != NULL;
+
+ if (pending == NULL)
+ {
+ /* Not cached and no pending request, create it */
+ *request = g_new0 (AsyncTextureLoadData, 1);
+ if (policy != ST_TEXTURE_CACHE_POLICY_NONE)
+ g_hash_table_insert (cache->priv->outstanding_requests, g_strdup (key), *request);
+ }
+ else
+ *request = pending;
+
+ /* Regardless of whether there was a pending request, prepend our texture here. */
+ (*request)->actors = g_slist_prepend ((*request)->actors, g_object_ref (actor));
+
+ return had_pending;
+}
+
+/**
+ * st_texture_cache_load_gicon:
+ * @cache: A #StTextureCache
+ * @theme_node: (nullable): The #StThemeNode to use for colors, or %NULL
+ * if the icon must not be recolored
+ * @icon: the #GIcon to load
+ * @size: Size of themed
+ * @paint_scale: Scale factor of display
+ * @resource_scale: Resource scale factor
+ *
+ * This method returns a new #ClutterActor for a given #GIcon. If the
+ * icon isn't loaded already, the texture will be filled
+ * asynchronously.
+ *
+ * Returns: (transfer none) (nullable): A new #ClutterActor for the icon, or %NULL if not found
+ */
+ClutterActor *
+st_texture_cache_load_gicon (StTextureCache *cache,
+ StThemeNode *theme_node,
+ GIcon *icon,
+ gint size,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ AsyncTextureLoadData *request;
+ ClutterActor *actor;
+ gint scale;
+ char *gicon_string;
+ g_autofree char *key = NULL;
+ float actor_size;
+ GtkIconTheme *theme;
+ StTextureCachePolicy policy;
+ StIconColors *colors = NULL;
+ StIconStyle icon_style = ST_ICON_STYLE_REQUESTED;
+ GtkIconLookupFlags lookup_flags;
+
+ actor_size = size * paint_scale;
+
+ if (ST_IS_IMAGE_CONTENT (icon))
+ {
+ int width, height;
+
+ g_object_get (G_OBJECT (icon),
+ "preferred-width", &width,
+ "preferred-height", &height,
+ NULL);
+ if (width == 0 && height == 0)
+ return NULL;
+
+ return g_object_new (CLUTTER_TYPE_ACTOR,
+ "content-gravity", CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT,
+ "width", actor_size,
+ "height", actor_size,
+ "content", CLUTTER_CONTENT (icon),
+ NULL);
+ }
+
+ if (theme_node)
+ {
+ colors = st_theme_node_get_icon_colors (theme_node);
+ icon_style = st_theme_node_get_icon_style (theme_node);
+ }
+
+ /* Do theme lookups in the main thread to avoid thread-unsafety */
+ theme = cache->priv->icon_theme;
+
+ lookup_flags = GTK_ICON_LOOKUP_USE_BUILTIN;
+
+ if (icon_style == ST_ICON_STYLE_REGULAR)
+ lookup_flags |= GTK_ICON_LOOKUP_FORCE_REGULAR;
+ else if (icon_style == ST_ICON_STYLE_SYMBOLIC)
+ lookup_flags |= GTK_ICON_LOOKUP_FORCE_SYMBOLIC;
+
+ if (clutter_get_default_text_direction () == CLUTTER_TEXT_DIRECTION_RTL)
+ lookup_flags |= GTK_ICON_LOOKUP_DIR_RTL;
+ else
+ lookup_flags |= GTK_ICON_LOOKUP_DIR_LTR;
+
+ scale = ceilf (paint_scale * resource_scale);
+
+ gicon_string = g_icon_to_string (icon);
+ /* A return value of NULL indicates that the icon can not be serialized,
+ * so don't have a unique identifier for it as a cache key, and thus can't
+ * be cached. If it is cacheable, we hardcode a policy of FOREVER here for
+ * now; we should actually blow this away on icon theme changes probably */
+ policy = gicon_string != NULL ? ST_TEXTURE_CACHE_POLICY_FOREVER
+ : ST_TEXTURE_CACHE_POLICY_NONE;
+ if (colors)
+ {
+ /* This raises some doubts about the practice of using string keys */
+ key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d,scale=%d,style=%d,colors=%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x",
+ gicon_string, size, scale, icon_style,
+ colors->foreground.red, colors->foreground.blue, colors->foreground.green, colors->foreground.alpha,
+ colors->warning.red, colors->warning.blue, colors->warning.green, colors->warning.alpha,
+ colors->error.red, colors->error.blue, colors->error.green, colors->error.alpha,
+ colors->success.red, colors->success.blue, colors->success.green, colors->success.alpha);
+ }
+ else
+ {
+ key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d,scale=%d,style=%d",
+ gicon_string, size, scale, icon_style);
+ }
+ g_free (gicon_string);
+
+ actor = create_invisible_actor ();
+ clutter_actor_set_content_gravity (actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
+ clutter_actor_set_size (actor, actor_size, actor_size);
+ if (!ensure_request (cache, key, policy, &request, actor))
+ {
+ /* Else, make a new request */
+ GtkIconInfo *info;
+
+ info = gtk_icon_theme_lookup_by_gicon_for_scale (theme, icon,
+ size, scale,
+ lookup_flags);
+ if (info == NULL)
+ {
+ g_hash_table_remove (cache->priv->outstanding_requests, key);
+ texture_load_data_free (request);
+ g_object_unref (actor);
+ return NULL;
+ }
+
+ request->cache = cache;
+ /* Transfer ownership of key */
+ request->key = g_steal_pointer (&key);
+ request->policy = policy;
+ request->colors = colors ? st_icon_colors_ref (colors) : NULL;
+ request->icon_info = info;
+ request->width = request->height = size;
+ request->paint_scale = paint_scale;
+ request->resource_scale = resource_scale;
+
+ load_texture_async (cache, request);
+ }
+
+ return actor;
+}
+
+static ClutterActor *
+load_from_pixbuf (GdkPixbuf *pixbuf,
+ int paint_scale,
+ float resource_scale)
+{
+ g_autoptr(ClutterContent) image = NULL;
+ ClutterActor *actor;
+
+ image = pixbuf_to_st_content_image (pixbuf, -1, -1, paint_scale, resource_scale);
+
+ actor = g_object_new (CLUTTER_TYPE_ACTOR,
+ "request-mode", CLUTTER_REQUEST_CONTENT_SIZE,
+ NULL);
+ clutter_actor_set_content (actor, image);
+
+ return actor;
+}
+
+static void
+hash_table_remove_with_scales (GHashTable *hash,
+ GList *scales,
+ const char *base_key)
+{
+ GList *l;
+
+ for (l = scales; l; l = l->next)
+ {
+ double scale = *((double *)l->data);
+ g_autofree char *key = NULL;
+ key = g_strdup_printf ("%s%f", base_key, scale);
+ g_hash_table_remove (hash, key);
+ }
+}
+
+static void
+hash_table_insert_scale (GHashTable *hash,
+ double scale)
+{
+ double *saved_scale;
+
+ if (g_hash_table_contains (hash, &scale))
+ return;
+
+ saved_scale = g_new (double, 1);
+ *saved_scale = scale;
+
+ g_hash_table_add (hash, saved_scale);
+}
+
+static void
+file_changed_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ StTextureCache *cache = user_data;
+ char *key;
+ guint file_hash;
+ g_autoptr (GList) scales = NULL;
+
+ if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+ return;
+
+ file_hash = g_file_hash (file);
+ scales = g_hash_table_get_keys (cache->priv->used_scales);
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u", file_hash);
+ g_hash_table_remove (cache->priv->keyed_cache, key);
+ hash_table_remove_with_scales (cache->priv->keyed_cache, scales, key);
+ g_free (key);
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE_FOR_CAIRO "%u", file_hash);
+ g_hash_table_remove (cache->priv->keyed_surface_cache, key);
+ hash_table_remove_with_scales (cache->priv->keyed_surface_cache, scales, key);
+ g_free (key);
+
+ g_signal_emit (cache, signals[TEXTURE_FILE_CHANGED], 0, file);
+}
+
+static void
+ensure_monitor_for_file (StTextureCache *cache,
+ GFile *file)
+{
+ StTextureCachePrivate *priv = cache->priv;
+
+ /* No point in trying to monitor files that are part of a
+ * GResource, since it does not support file monitoring.
+ */
+ if (g_file_has_uri_scheme (file, "resource"))
+ return;
+
+ if (g_hash_table_lookup (priv->file_monitors, file) == NULL)
+ {
+ GFileMonitor *monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE,
+ NULL, NULL);
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (file_changed_cb), cache);
+ g_hash_table_insert (priv->file_monitors, g_object_ref (file), monitor);
+ }
+}
+
+typedef struct {
+ GFile *gfile;
+ gint grid_width, grid_height;
+ gint paint_scale;
+ gfloat resource_scale;
+ ClutterActor *actor;
+ GCancellable *cancellable;
+ GFunc load_callback;
+ gpointer load_callback_data;
+} AsyncImageData;
+
+static void
+on_data_destroy (gpointer data)
+{
+ AsyncImageData *d = (AsyncImageData *)data;
+ g_object_unref (d->gfile);
+ g_object_unref (d->actor);
+ g_object_unref (d->cancellable);
+ g_free (d);
+}
+
+static void
+on_sliced_image_actor_destroyed (ClutterActor *actor,
+ gpointer data)
+{
+ GTask *task = data;
+ GCancellable *cancellable = g_task_get_cancellable (task);
+
+ g_cancellable_cancel (cancellable);
+}
+
+static void
+on_sliced_image_loaded (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GObject *cache = source_object;
+ AsyncImageData *data = (AsyncImageData *)user_data;
+ GTask *task = G_TASK (res);
+ GList *list, *pixbufs;
+
+ if (g_task_had_error (task) || g_cancellable_is_cancelled (data->cancellable))
+ return;
+
+ pixbufs = g_task_propagate_pointer (task, NULL);
+
+ for (list = pixbufs; list; list = list->next)
+ {
+ ClutterActor *actor = load_from_pixbuf (GDK_PIXBUF (list->data),
+ data->paint_scale,
+ data->resource_scale);
+ clutter_actor_hide (actor);
+ clutter_actor_add_child (data->actor, actor);
+ }
+
+ g_list_free_full (pixbufs, g_object_unref);
+
+ g_signal_handlers_disconnect_by_func (data->actor,
+ on_sliced_image_actor_destroyed,
+ task);
+
+ if (data->load_callback != NULL)
+ data->load_callback (cache, data->load_callback_data);
+}
+
+static void
+free_glist_unref_gobjects (gpointer p)
+{
+ g_list_free_full (p, g_object_unref);
+}
+
+static void
+on_loader_size_prepared (GdkPixbufLoader *loader,
+ gint width,
+ gint height,
+ gpointer user_data)
+{
+ AsyncImageData *data = user_data;
+ int scale = ceilf (data->paint_scale * data->resource_scale);
+
+ gdk_pixbuf_loader_set_size (loader, width * scale, height * scale);
+}
+
+static void
+load_sliced_image (GTask *result,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ AsyncImageData *data;
+ GList *res = NULL;
+ GdkPixbuf *pix;
+ gint width, height, y, x;
+ gint scale_factor;
+ GdkPixbufLoader *loader;
+ GError *error = NULL;
+ gchar *buffer = NULL;
+ gsize length;
+
+ g_assert (cancellable);
+
+ data = task_data;
+ g_assert (data);
+
+ loader = gdk_pixbuf_loader_new ();
+ g_signal_connect (loader, "size-prepared", G_CALLBACK (on_loader_size_prepared), data);
+
+ if (!g_file_load_contents (data->gfile, cancellable, &buffer, &length, NULL, &error))
+ {
+ g_warning ("Failed to open sliced image: %s", error->message);
+ goto out;
+ }
+
+ if (!gdk_pixbuf_loader_write (loader, (const guchar *) buffer, length, &error))
+ {
+ g_warning ("Failed to load image: %s", error->message);
+ goto out;
+ }
+
+ if (!gdk_pixbuf_loader_close (loader, NULL))
+ goto out;
+
+ pix = gdk_pixbuf_loader_get_pixbuf (loader);
+ width = gdk_pixbuf_get_width (pix);
+ height = gdk_pixbuf_get_height (pix);
+ scale_factor = ceilf (data->paint_scale * data->resource_scale);
+ for (y = 0; y < height; y += data->grid_height * scale_factor)
+ {
+ for (x = 0; x < width; x += data->grid_width * scale_factor)
+ {
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_subpixbuf (pix, x, y,
+ data->grid_width * scale_factor,
+ data->grid_height * scale_factor);
+ g_assert (pixbuf != NULL);
+ res = g_list_append (res, pixbuf);
+ }
+ }
+
+ out:
+ /* We don't need the original pixbuf anymore, which is owned by the loader,
+ * though the subpixbufs will hold a reference. */
+ g_object_unref (loader);
+ g_free (buffer);
+ g_clear_pointer (&error, g_error_free);
+ g_task_return_pointer (result, res, free_glist_unref_gobjects);
+}
+
+/**
+ * st_texture_cache_load_sliced_image:
+ * @cache: A #StTextureCache
+ * @file: A #GFile
+ * @grid_width: Width in pixels
+ * @grid_height: Height in pixels
+ * @paint_scale: Scale factor of the display
+ * @load_callback: (scope async) (nullable): Function called when the image is loaded, or %NULL
+ * @user_data: Data to pass to the load callback
+ *
+ * This function reads a single image file which contains multiple images internally.
+ * The image file will be divided using @grid_width and @grid_height;
+ * note that the dimensions of the image loaded from @path
+ * should be a multiple of the specified grid dimensions.
+ *
+ * Returns: (transfer none): A new #ClutterActor
+ */
+ClutterActor *
+st_texture_cache_load_sliced_image (StTextureCache *cache,
+ GFile *file,
+ gint grid_width,
+ gint grid_height,
+ gint paint_scale,
+ gfloat resource_scale,
+ GFunc load_callback,
+ gpointer user_data)
+{
+ AsyncImageData *data;
+ GTask *result;
+ ClutterActor *actor = clutter_actor_new ();
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_assert (paint_scale > 0);
+ g_assert (resource_scale > 0);
+
+ data = g_new0 (AsyncImageData, 1);
+ data->grid_width = grid_width;
+ data->grid_height = grid_height;
+ data->paint_scale = paint_scale;
+ data->resource_scale = resource_scale;
+ data->gfile = g_object_ref (file);
+ data->actor = actor;
+ data->cancellable = cancellable;
+ data->load_callback = load_callback;
+ data->load_callback_data = user_data;
+ g_object_ref (G_OBJECT (actor));
+
+ result = g_task_new (cache, cancellable, on_sliced_image_loaded, data);
+
+ g_signal_connect (actor, "destroy",
+ G_CALLBACK (on_sliced_image_actor_destroyed), result);
+
+ g_task_set_task_data (result, data, on_data_destroy);
+ g_task_run_in_thread (result, load_sliced_image);
+
+ g_object_unref (result);
+
+ return actor;
+}
+
+/**
+ * st_texture_cache_load_file_async:
+ * @cache: A #StTextureCache
+ * @file: a #GFile of the image file from which to create a pixbuf
+ * @available_width: available width for the image, can be -1 if not limited
+ * @available_height: available height for the image, can be -1 if not limited
+ * @paint_scale: scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * Asynchronously load an image. Initially, the returned texture will have a natural
+ * size of zero. At some later point, either the image will be loaded successfully
+ * and at that point size will be negotiated, or upon an error, no image will be set.
+ *
+ * Returns: (transfer none): A new #ClutterActor with no image loaded initially.
+ */
+ClutterActor *
+st_texture_cache_load_file_async (StTextureCache *cache,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale)
+{
+ ClutterActor *actor;
+ AsyncTextureLoadData *request;
+ StTextureCachePolicy policy;
+ gchar *key;
+ int scale;
+
+ scale = ceilf (paint_scale * resource_scale);
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u%d", g_file_hash (file), scale);
+
+ policy = ST_TEXTURE_CACHE_POLICY_NONE; /* XXX */
+
+ actor = create_invisible_actor ();
+
+ if (ensure_request (cache, key, policy, &request, actor))
+ {
+ /* If there's an outstanding request, we've just added ourselves to it */
+ g_free (key);
+ }
+ else
+ {
+ /* Else, make a new request */
+
+ request->cache = cache;
+ /* Transfer ownership of key */
+ request->key = key;
+ request->file = g_object_ref (file);
+ request->policy = policy;
+ request->width = available_width;
+ request->height = available_height;
+ request->paint_scale = paint_scale;
+ request->resource_scale = resource_scale;
+
+ load_texture_async (cache, request);
+ }
+
+ ensure_monitor_for_file (cache, file);
+
+ return actor;
+}
+
+static CoglTexture *
+st_texture_cache_load_file_sync_to_cogl_texture (StTextureCache *cache,
+ StTextureCachePolicy policy,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale,
+ GError **error)
+{
+ ClutterContent *image;
+ CoglTexture *texdata;
+ GdkPixbuf *pixbuf;
+ char *key;
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u%f", g_file_hash (file), resource_scale);
+
+ texdata = NULL;
+ image = g_hash_table_lookup (cache->priv->keyed_cache, key);
+
+ if (image == NULL)
+ {
+ pixbuf = impl_load_pixbuf_file (file, available_width, available_height,
+ paint_scale, resource_scale, error);
+ if (!pixbuf)
+ goto out;
+
+ image = pixbuf_to_st_content_image (pixbuf,
+ available_height, available_width,
+ paint_scale, resource_scale);
+ g_object_unref (pixbuf);
+
+ if (!image)
+ goto out;
+
+ if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ {
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), image);
+ hash_table_insert_scale (cache->priv->used_scales, (double)resource_scale);
+ }
+ }
+
+ /* Because the texture is loaded synchronously, we won't call
+ * clutter_image_set_data(), so it's safe to use the texture
+ * of ClutterImage here. */
+ texdata = clutter_image_get_texture (CLUTTER_IMAGE (image));
+ cogl_object_ref (texdata);
+
+ ensure_monitor_for_file (cache, file);
+
+out:
+ g_free (key);
+ return texdata;
+}
+
+static cairo_surface_t *
+st_texture_cache_load_file_sync_to_cairo_surface (StTextureCache *cache,
+ StTextureCachePolicy policy,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale,
+ GError **error)
+{
+ cairo_surface_t *surface;
+ GdkPixbuf *pixbuf;
+ char *key;
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE_FOR_CAIRO "%u%f", g_file_hash (file), resource_scale);
+
+ surface = g_hash_table_lookup (cache->priv->keyed_surface_cache, key);
+
+ if (surface == NULL)
+ {
+ pixbuf = impl_load_pixbuf_file (file, available_width, available_height,
+ paint_scale, resource_scale, error);
+ if (!pixbuf)
+ goto out;
+
+ surface = pixbuf_to_cairo_surface (pixbuf);
+ g_object_unref (pixbuf);
+
+ if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ {
+ cairo_surface_reference (surface);
+ g_hash_table_insert (cache->priv->keyed_surface_cache,
+ g_strdup (key), surface);
+ hash_table_insert_scale (cache->priv->used_scales, (double)resource_scale);
+ }
+ }
+ else
+ cairo_surface_reference (surface);
+
+ ensure_monitor_for_file (cache, file);
+
+out:
+ g_free (key);
+ return surface;
+}
+
+/**
+ * st_texture_cache_load_file_to_cogl_texture: (skip)
+ * @cache: A #StTextureCache
+ * @file: A #GFile in supported image format
+ * @paint_scale: Scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * This function synchronously loads the given file path
+ * into a COGL texture. On error, a warning is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): a new #CoglTexture
+ */
+CoglTexture *
+st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ CoglTexture *texture;
+ GError *error = NULL;
+
+ texture = st_texture_cache_load_file_sync_to_cogl_texture (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
+ file, -1, -1, paint_scale, resource_scale,
+ &error);
+
+ if (texture == NULL)
+ {
+ char *uri = g_file_get_uri (file);
+ g_warning ("Failed to load %s: %s", uri, error->message);
+ g_clear_error (&error);
+ g_free (uri);
+ }
+
+ return texture;
+}
+
+/**
+ * st_texture_cache_load_file_to_cairo_surface:
+ * @cache: A #StTextureCache
+ * @file: A #GFile in supported image format
+ * @paint_scale: Scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * This function synchronously loads the given file path
+ * into a cairo surface. On error, a warning is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): a new #cairo_surface_t
+ */
+cairo_surface_t *
+st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ cairo_surface_t *surface;
+ GError *error = NULL;
+
+ surface = st_texture_cache_load_file_sync_to_cairo_surface (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
+ file, -1, -1, paint_scale, resource_scale,
+ &error);
+
+ if (surface == NULL)
+ {
+ char *uri = g_file_get_uri (file);
+ g_warning ("Failed to load %s: %s", uri, error->message);
+ g_clear_error (&error);
+ g_free (uri);
+ }
+
+ return surface;
+}
+
+static StTextureCache *instance = NULL;
+
+/**
+ * st_texture_cache_get_default:
+ *
+ * Returns: (transfer none): The global texture cache
+ */
+StTextureCache*
+st_texture_cache_get_default (void)
+{
+ if (instance == NULL)
+ instance = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL);
+ return instance;
+}
+
+/**
+ * st_texture_cache_rescan_icon_theme:
+ *
+ * Rescan the current icon theme, if necessary.
+ *
+ * Returns: %TRUE if the icon theme has changed and needed to be reloaded.
+ */
+gboolean
+st_texture_cache_rescan_icon_theme (StTextureCache *cache)
+{
+ StTextureCachePrivate *priv = cache->priv;
+
+ return gtk_icon_theme_rescan_if_needed (priv->icon_theme);
+}
diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h
new file mode 100644
index 0000000..55d8495
--- /dev/null
+++ b/src/st/st-texture-cache.h
@@ -0,0 +1,120 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-texture-cache.h: Object for loading and caching images as textures
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010, Maxim Ermilov
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_TEXTURE_CACHE_H__
+#define __ST_TEXTURE_CACHE_H__
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <clutter/clutter.h>
+
+#include <st/st-types.h>
+#include <st/st-theme-node.h>
+#include <st/st-widget.h>
+
+#define ST_TYPE_TEXTURE_CACHE (st_texture_cache_get_type ())
+G_DECLARE_FINAL_TYPE (StTextureCache, st_texture_cache,
+ ST, TEXTURE_CACHE, GObject)
+
+typedef struct _StTextureCachePrivate StTextureCachePrivate;
+
+struct _StTextureCache
+{
+ GObject parent;
+
+ StTextureCachePrivate *priv;
+};
+
+typedef enum {
+ ST_TEXTURE_CACHE_POLICY_NONE,
+ ST_TEXTURE_CACHE_POLICY_FOREVER
+} StTextureCachePolicy;
+
+StTextureCache* st_texture_cache_get_default (void);
+
+ClutterActor *
+st_texture_cache_load_sliced_image (StTextureCache *cache,
+ GFile *file,
+ gint grid_width,
+ gint grid_height,
+ gint paint_scale,
+ gfloat resource_scale,
+ GFunc load_callback,
+ gpointer user_data);
+
+GIcon *st_texture_cache_bind_cairo_surface_property (StTextureCache *cache,
+ GObject *object,
+ const char *property_name);
+GIcon *
+st_texture_cache_load_cairo_surface_to_gicon (StTextureCache *cache,
+ cairo_surface_t *surface);
+
+ClutterActor *st_texture_cache_load_gicon (StTextureCache *cache,
+ StThemeNode *theme_node,
+ GIcon *icon,
+ gint size,
+ gint paint_scale,
+ gfloat resource_scale);
+
+ClutterActor *st_texture_cache_load_file_async (StTextureCache *cache,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale);
+
+CoglTexture *st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale);
+
+cairo_surface_t *st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale);
+
+/**
+ * StTextureCacheLoader: (skip)
+ * @cache: a #StTextureCache
+ * @key: Unique identifier for this texture
+ * @data: Callback user data
+ * @error: A #GError
+ *
+ * See st_texture_cache_load(). Implementations should return a
+ * texture handle for the given key, or set @error.
+ *
+ */
+typedef CoglTexture * (*StTextureCacheLoader) (StTextureCache *cache, const char *key, void *data, GError **error);
+
+CoglTexture * st_texture_cache_load (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ StTextureCacheLoader load,
+ void *data,
+ GError **error);
+
+gboolean st_texture_cache_rescan_icon_theme (StTextureCache *cache);
+
+#endif /* __ST_TEXTURE_CACHE_H__ */
diff --git a/src/st/st-theme-context.c b/src/st/st-theme-context.c
new file mode 100644
index 0000000..4055786
--- /dev/null
+++ b/src/st/st-theme-context.c
@@ -0,0 +1,492 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-context.c: holds global information about a tree of styled objects
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Florian Müllner
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "st-private.h"
+#include "st-settings.h"
+#include "st-texture-cache.h"
+#include "st-theme.h"
+#include "st-theme-context.h"
+#include "st-theme-node-private.h"
+
+struct _StThemeContext {
+ GObject parent;
+
+ PangoFontDescription *font;
+ StThemeNode *root_node;
+ StTheme *theme;
+
+ /* set of StThemeNode */
+ GHashTable *nodes;
+
+ gulong stylesheets_changed_id;
+
+ int scale_factor;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SCALE_FACTOR,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (StThemeContext, st_theme_context, G_TYPE_OBJECT)
+
+static PangoFontDescription *get_interface_font_description (void);
+static void on_font_name_changed (StSettings *settings,
+ GParamSpec *pspec,
+ StThemeContext *context);
+static void on_icon_theme_changed (StTextureCache *cache,
+ StThemeContext *context);
+static void st_theme_context_changed (StThemeContext *context);
+
+static void st_theme_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void st_theme_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void
+st_theme_context_set_scale_factor (StThemeContext *context,
+ int scale_factor)
+{
+ if (scale_factor == context->scale_factor)
+ return;
+
+ context->scale_factor = scale_factor;
+ g_object_notify_by_pspec (G_OBJECT (context), props[PROP_SCALE_FACTOR]);
+ st_theme_context_changed (context);
+}
+
+
+static void
+st_theme_context_finalize (GObject *object)
+{
+ StThemeContext *context = ST_THEME_CONTEXT (object);
+
+ g_signal_handlers_disconnect_by_func (st_settings_get (),
+ (gpointer) on_font_name_changed,
+ context);
+ g_signal_handlers_disconnect_by_func (st_texture_cache_get_default (),
+ (gpointer) on_icon_theme_changed,
+ context);
+ g_signal_handlers_disconnect_by_func (clutter_get_default_backend (),
+ (gpointer) st_theme_context_changed,
+ context);
+
+ g_clear_signal_handler (&context->stylesheets_changed_id, context->theme);
+
+ if (context->nodes)
+ g_hash_table_unref (context->nodes);
+ if (context->root_node)
+ g_object_unref (context->root_node);
+ if (context->theme)
+ g_object_unref (context->theme);
+
+ pango_font_description_free (context->font);
+
+ G_OBJECT_CLASS (st_theme_context_parent_class)->finalize (object);
+}
+
+static void
+st_theme_context_class_init (StThemeContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = st_theme_context_set_property;
+ object_class->get_property = st_theme_context_get_property;
+ object_class->finalize = st_theme_context_finalize;
+
+ /**
+ * StThemeContext:scale-factor:
+ *
+ * The scaling factor used for HiDPI scaling.
+ */
+ props[PROP_SCALE_FACTOR] =
+ g_param_spec_int ("scale-factor",
+ "Scale factor",
+ "Integer scale factor used for HiDPI scaling",
+ 0, G_MAXINT, 1,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+ /**
+ * StThemeContext::changed:
+ * @self: a #StThemeContext
+ *
+ * Emitted when the icon theme, font, resolution, scale factor or the current
+ * theme's custom stylesheets change.
+ */
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_theme_context_init (StThemeContext *context)
+{
+ context->font = get_interface_font_description ();
+
+ g_signal_connect (st_settings_get (),
+ "notify::font-name",
+ G_CALLBACK (on_font_name_changed),
+ context);
+ g_signal_connect (st_texture_cache_get_default (),
+ "icon-theme-changed",
+ G_CALLBACK (on_icon_theme_changed),
+ context);
+ g_signal_connect_swapped (clutter_get_default_backend (),
+ "resolution-changed",
+ G_CALLBACK (st_theme_context_changed),
+ context);
+
+ context->nodes = g_hash_table_new_full ((GHashFunc) st_theme_node_hash,
+ (GEqualFunc) st_theme_node_equal,
+ g_object_unref, NULL);
+ context->scale_factor = 1;
+}
+
+static void
+st_theme_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StThemeContext *context = ST_THEME_CONTEXT (object);
+
+ switch (prop_id)
+ {
+ case PROP_SCALE_FACTOR:
+ st_theme_context_set_scale_factor (context, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_theme_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StThemeContext *context = ST_THEME_CONTEXT (object);
+
+ switch (prop_id)
+ {
+ case PROP_SCALE_FACTOR:
+ g_value_set_int (value, context->scale_factor);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * st_theme_context_new:
+ *
+ * Create a new theme context not associated with any #ClutterStage.
+ * This can be useful in testing scenarios, or if using StThemeContext
+ * with something other than #ClutterActor objects, but you generally
+ * should use st_theme_context_get_for_stage() instead.
+ *
+ * Returns: (transfer full): a new #StThemeContext
+ */
+StThemeContext *
+st_theme_context_new (void)
+{
+ StThemeContext *context;
+
+ context = g_object_new (ST_TYPE_THEME_CONTEXT, NULL);
+
+ return context;
+}
+
+static PangoFontDescription *
+get_interface_font_description (void)
+{
+ StSettings *settings = st_settings_get ();
+ g_autofree char *font_name = NULL;
+
+ g_object_get (settings, "font-name", &font_name, NULL);
+ return pango_font_description_from_string (font_name);
+}
+
+static void
+on_stage_destroy (ClutterStage *stage)
+{
+ StThemeContext *context = st_theme_context_get_for_stage (stage);
+
+ g_object_set_data (G_OBJECT (stage), "st-theme-context", NULL);
+ g_object_unref (context);
+}
+
+static void
+st_theme_context_changed (StThemeContext *context)
+{
+ StThemeNode *old_root = context->root_node;
+ context->root_node = NULL;
+ g_hash_table_remove_all (context->nodes);
+
+ g_signal_emit (context, signals[CHANGED], 0);
+
+ if (old_root)
+ g_object_unref (old_root);
+}
+
+static void
+on_font_name_changed (StSettings *settings,
+ GParamSpec *pspect,
+ StThemeContext *context)
+{
+ PangoFontDescription *font_desc = get_interface_font_description ();
+ st_theme_context_set_font (context, font_desc);
+
+ pango_font_description_free (font_desc);
+}
+
+static gboolean
+changed_idle (gpointer userdata)
+{
+ st_theme_context_changed (userdata);
+ return FALSE;
+}
+
+static void
+on_icon_theme_changed (StTextureCache *cache,
+ StThemeContext *context)
+{
+ guint id;
+
+ /* Note that an icon theme change isn't really a change of the StThemeContext;
+ * the style information has changed. But since the style factors into the
+ * icon_name => icon lookup, faking a theme context change is a good way
+ * to force users such as StIcon to look up icons again.
+ */
+ id = g_idle_add ((GSourceFunc) changed_idle, context);
+ g_source_set_name_by_id (id, "[gnome-shell] changed_idle");
+}
+
+/**
+ * st_theme_context_get_for_stage:
+ * @stage: a #ClutterStage
+ *
+ * Gets a singleton theme context associated with the stage.
+ *
+ * Returns: (transfer none): the singleton theme context for the stage
+ */
+StThemeContext *
+st_theme_context_get_for_stage (ClutterStage *stage)
+{
+ StThemeContext *context;
+
+ g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
+
+ context = g_object_get_data (G_OBJECT (stage), "st-theme-context");
+ if (context)
+ return context;
+
+ context = st_theme_context_new ();
+ g_object_set_data (G_OBJECT (stage), "st-theme-context", context);
+ g_signal_connect (stage, "destroy",
+ G_CALLBACK (on_stage_destroy), NULL);
+
+ return context;
+}
+
+/**
+ * st_theme_context_set_theme:
+ * @context: a #StThemeContext
+ * @theme: a #StTheme
+ *
+ * Sets the default set of theme stylesheets for the context. This theme will
+ * be used for the root node and for nodes descending from it, unless some other
+ * style is explicitly specified.
+ */
+void
+st_theme_context_set_theme (StThemeContext *context,
+ StTheme *theme)
+{
+ g_return_if_fail (ST_IS_THEME_CONTEXT (context));
+ g_return_if_fail (theme == NULL || ST_IS_THEME (theme));
+
+ if (context->theme != theme)
+ {
+ if (context->theme)
+ g_clear_signal_handler (&context->stylesheets_changed_id, context->theme);
+
+ g_set_object (&context->theme, theme);
+
+ if (context->theme)
+ {
+ context->stylesheets_changed_id =
+ g_signal_connect_swapped (context->theme,
+ "custom-stylesheets-changed",
+ G_CALLBACK (st_theme_context_changed),
+ context);
+ }
+
+ st_theme_context_changed (context);
+ }
+}
+
+/**
+ * st_theme_context_get_theme:
+ * @context: a #StThemeContext
+ *
+ * Gets the default theme for the context. See st_theme_context_set_theme()
+ *
+ * Returns: (transfer none): the default theme for the context
+ */
+StTheme *
+st_theme_context_get_theme (StThemeContext *context)
+{
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+
+ return context->theme;
+}
+
+/**
+ * st_theme_context_set_font:
+ * @context: a #StThemeContext
+ * @font: the default font for theme context
+ *
+ * Sets the default font for the theme context. This is the font that
+ * is inherited by the root node of the tree of theme nodes. If the
+ * font is not overridden, then this font will be used. If the font is
+ * partially modified (for example, with 'font-size: 110%'), then that
+ * modification is based on this font.
+ */
+void
+st_theme_context_set_font (StThemeContext *context,
+ const PangoFontDescription *font)
+{
+ g_return_if_fail (ST_IS_THEME_CONTEXT (context));
+ g_return_if_fail (font != NULL);
+
+ if (context->font == font ||
+ pango_font_description_equal (context->font, font))
+ return;
+
+ pango_font_description_free (context->font);
+ context->font = pango_font_description_copy (font);
+ st_theme_context_changed (context);
+}
+
+/**
+ * st_theme_context_get_font:
+ * @context: a #StThemeContext
+ *
+ * Gets the default font for the theme context. See st_theme_context_set_font().
+ *
+ * Returns: the default font for the theme context.
+ */
+const PangoFontDescription *
+st_theme_context_get_font (StThemeContext *context)
+{
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+
+ return context->font;
+}
+
+/**
+ * st_theme_context_get_root_node:
+ * @context: a #StThemeContext
+ *
+ * Gets the root node of the tree of theme style nodes that associated with this
+ * context. For the node tree associated with a stage, this node represents
+ * styles applied to the stage itself.
+ *
+ * Returns: (transfer none): the root node of the context's style tree
+ */
+StThemeNode *
+st_theme_context_get_root_node (StThemeContext *context)
+{
+ if (context->root_node == NULL)
+ context->root_node = st_theme_node_new (context, NULL, context->theme,
+ G_TYPE_NONE, NULL, NULL, NULL, NULL);
+
+ return context->root_node;
+}
+
+/**
+ * st_theme_context_intern_node:
+ * @context: a #StThemeContext
+ * @node: a #StThemeNode
+ *
+ * Return an existing node matching @node, or if that isn't possible,
+ * @node itself.
+ *
+ * Returns: (transfer none): a node with the same properties as @node
+ */
+StThemeNode *
+st_theme_context_intern_node (StThemeContext *context,
+ StThemeNode *node)
+{
+ StThemeNode *mine = g_hash_table_lookup (context->nodes, node);
+
+ /* this might be node or not - it doesn't actually matter */
+ if (mine != NULL)
+ return mine;
+
+ g_hash_table_add (context->nodes, g_object_ref (node));
+ return node;
+}
+
+/**
+ * st_theme_context_get_scale_factor:
+ * @context: a #StThemeContext
+ *
+ * Return the current scale factor of @context.
+ *
+ * Returns: an integer scale factor
+ */
+int
+st_theme_context_get_scale_factor (StThemeContext *context)
+{
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), -1);
+
+ return context->scale_factor;
+}
diff --git a/src/st/st-theme-context.h b/src/st/st-theme-context.h
new file mode 100644
index 0000000..165ce25
--- /dev/null
+++ b/src/st/st-theme-context.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-context.c: holds global information about a tree of styled objects
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Florian Müllner
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_CONTEXT_H__
+#define __ST_THEME_CONTEXT_H__
+
+#include <clutter/clutter.h>
+#include <pango/pango.h>
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-theme-context
+ * @short_description: holds global information about a tree of styled objects
+ *
+ * #StThemeContext is responsible for managing information global to a tree of styled objects,
+ * such as the set of stylesheets or the default font. In normal usage, a #StThemeContext
+ * is bound to a #ClutterStage; a singleton #StThemeContext can be obtained for a #ClutterStage
+ * by using st_theme_context_get_for_stage().
+ */
+
+#define ST_TYPE_THEME_CONTEXT (st_theme_context_get_type ())
+G_DECLARE_FINAL_TYPE (StThemeContext, st_theme_context,
+ ST, THEME_CONTEXT, GObject)
+
+StThemeContext *st_theme_context_new (void);
+StThemeContext *st_theme_context_get_for_stage (ClutterStage *stage);
+
+void st_theme_context_set_theme (StThemeContext *context,
+ StTheme *theme);
+StTheme * st_theme_context_get_theme (StThemeContext *context);
+
+void st_theme_context_set_font (StThemeContext *context,
+ const PangoFontDescription *font);
+const PangoFontDescription *st_theme_context_get_font (StThemeContext *context);
+
+StThemeNode * st_theme_context_get_root_node (StThemeContext *context);
+
+StThemeNode * st_theme_context_intern_node (StThemeContext *context,
+ StThemeNode *node);
+
+int st_theme_context_get_scale_factor (StThemeContext *context);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_CONTEXT_H__ */
diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c
new file mode 100644
index 0000000..72745ed
--- /dev/null
+++ b/src/st/st-theme-node-drawing.c
@@ -0,0 +1,2864 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-drawing.c: Code to draw themed elements
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Quentin "Sardem FF7" Glidic
+ *
+ * Contains code derived from:
+ * rectangle.c: Rounded rectangle.
+ * Copyright 2008 litl, LLC.
+ * st-texture-frame.h: Expandible texture actor
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "st-shadow.h"
+#include "st-private.h"
+#include "st-theme-private.h"
+#include "st-theme-context.h"
+#include "st-texture-cache.h"
+#include "st-theme-node-private.h"
+
+/****
+ * Rounded corners
+ ****/
+
+typedef struct {
+ ClutterColor color;
+ ClutterColor border_color_1;
+ ClutterColor border_color_2;
+ guint radius;
+ guint border_width_1;
+ guint border_width_2;
+ float resource_scale;
+} StCornerSpec;
+
+typedef enum {
+ ST_PAINT_BORDERS_MODE_COLOR,
+ ST_PAINT_BORDERS_MODE_SILHOUETTE
+} StPaintBordersMode;
+
+static void
+elliptical_arc (cairo_t *cr,
+ double x_center,
+ double y_center,
+ double x_radius,
+ double y_radius,
+ double angle1,
+ double angle2)
+{
+ cairo_save (cr);
+ cairo_translate (cr, x_center, y_center);
+ cairo_scale (cr, x_radius, y_radius);
+ cairo_arc (cr, 0, 0, 1.0, angle1, angle2);
+ cairo_restore (cr);
+}
+
+static CoglTexture *
+create_corner_material (StCornerSpec *corner)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ GError *error = NULL;
+ CoglTexture *texture;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ guint rowstride;
+ guint8 *data;
+ guint size;
+ guint logical_size;
+ guint max_border_width;
+ double device_scaling;
+
+ max_border_width = MAX(corner->border_width_2, corner->border_width_1);
+ logical_size = 2 * MAX(max_border_width, corner->radius);
+ size = ceilf (logical_size * corner->resource_scale);
+ rowstride = size * 4;
+ data = g_new0 (guint8, size * rowstride);
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ size, size,
+ rowstride);
+ device_scaling = (double) size / logical_size;
+ cairo_surface_set_device_scale (surface, device_scaling, device_scaling);
+ cr = cairo_create (surface);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_scale (cr, logical_size, logical_size);
+
+ if (max_border_width <= corner->radius)
+ {
+ double x_radius, y_radius;
+
+ if (max_border_width != 0)
+ {
+ cairo_set_source_rgba (cr,
+ corner->border_color_1.red / 255.,
+ corner->border_color_1.green / 255.,
+ corner->border_color_1.blue / 255.,
+ corner->border_color_1.alpha / 255.);
+
+ cairo_arc (cr, 0.5, 0.5, 0.5, 0, 2 * M_PI);
+ cairo_fill (cr);
+ }
+
+ cairo_set_source_rgba (cr,
+ corner->color.red / 255.,
+ corner->color.green / 255.,
+ corner->color.blue / 255.,
+ corner->color.alpha / 255.);
+
+ x_radius = 0.5 * (1.0 - (double) corner->border_width_2 / corner->radius);
+ y_radius = 0.5 * (1.0 - (double) corner->border_width_1 / corner->radius);
+
+ /* TOPRIGHT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ 3 * M_PI / 2, 2 * M_PI);
+
+ /* BOTTOMRIGHT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ 0, M_PI / 2);
+
+ /* TOPLEFT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ M_PI, 3 * M_PI / 2);
+
+ /* BOTTOMLEFT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ M_PI / 2, M_PI);
+
+ cairo_fill (cr);
+ }
+ else
+ {
+ double radius;
+
+ radius = (gdouble)corner->radius / max_border_width;
+
+ cairo_set_source_rgba (cr,
+ corner->border_color_1.red / 255.,
+ corner->border_color_1.green / 255.,
+ corner->border_color_1.blue / 255.,
+ corner->border_color_1.alpha / 255.);
+
+ cairo_arc (cr, radius, radius, radius, M_PI, 3 * M_PI / 2);
+ cairo_line_to (cr, 1.0 - radius, 0.0);
+ cairo_arc (cr, 1.0 - radius, radius, radius, 3 * M_PI / 2, 2 * M_PI);
+ cairo_line_to (cr, 1.0, 1.0 - radius);
+ cairo_arc (cr, 1.0 - radius, 1.0 - radius, radius, 0, M_PI / 2);
+ cairo_line_to (cr, radius, 1.0);
+ cairo_arc (cr, radius, 1.0 - radius, radius, M_PI / 2, M_PI);
+ cairo_fill (cr);
+ }
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (surface);
+
+ texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, size, size,
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ rowstride,
+ data,
+ &error));
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (data);
+
+ return texture;
+}
+
+static char *
+corner_to_string (StCornerSpec *corner)
+{
+ return g_strdup_printf ("st-theme-node-corner:%02x%02x%02x%02x,%02x%02x%02x%02x,%02x%02x%02x%02x,%u,%u,%u,%.4f",
+ corner->color.red, corner->color.blue, corner->color.green, corner->color.alpha,
+ corner->border_color_1.red, corner->border_color_1.green, corner->border_color_1.blue, corner->border_color_1.alpha,
+ corner->border_color_2.red, corner->border_color_2.green, corner->border_color_2.blue, corner->border_color_2.alpha,
+ corner->radius,
+ corner->border_width_1,
+ corner->border_width_2,
+ corner->resource_scale);
+}
+
+static CoglTexture *
+load_corner (StTextureCache *cache,
+ const char *key,
+ void *datap,
+ GError **error)
+{
+ return create_corner_material ((StCornerSpec *) datap);
+}
+
+/* To match the CSS specification, we want the border to look like it was
+ * drawn over the background. But actually drawing the border over the
+ * background will produce slightly bad antialiasing at the edges, so
+ * compute the effective border color instead.
+ */
+#define NORM(x) (t = (x) + 127, (t + (t >> 8)) >> 8)
+#define MULT(c,a) NORM(c*a)
+
+static void
+premultiply (ClutterColor *color)
+{
+ guint t;
+ color->red = MULT (color->red, color->alpha);
+ color->green = MULT (color->green, color->alpha);
+ color->blue = MULT (color->blue, color->alpha);
+}
+
+static void
+unpremultiply (ClutterColor *color)
+{
+ if (color->alpha != 0)
+ {
+ color->red = MIN((color->red * 255 + 127) / color->alpha, 255);
+ color->green = MIN((color->green * 255 + 127) / color->alpha, 255);
+ color->blue = MIN((color->blue * 255 + 127) / color->alpha, 255);
+ }
+}
+
+static void
+over (const ClutterColor *source,
+ const ClutterColor *destination,
+ ClutterColor *result)
+{
+ guint t;
+ ClutterColor src = *source;
+ ClutterColor dst = *destination;
+
+ premultiply (&src);
+ premultiply (&dst);
+
+ result->alpha = src.alpha + NORM ((255 - src.alpha) * dst.alpha);
+ result->red = src.red + NORM ((255 - src.alpha) * dst.red);
+ result->green = src.green + NORM ((255 - src.alpha) * dst.green);
+ result->blue = src.blue + NORM ((255 - src.alpha) * dst.blue);
+
+ unpremultiply (result);
+}
+
+/*
+ * st_theme_node_reduce_border_radius:
+ * @node: a #StThemeNode
+ * @width: The width of the box
+ * @height: The height of the box
+ * @corners: (array length=4) (out): reduced corners
+ *
+ * Implements the corner overlap algorithm mentioned at
+ * http://www.w3.org/TR/css3-background/#corner-overlap
+ */
+static void
+st_theme_node_reduce_border_radius (StThemeNode *node,
+ float width,
+ float height,
+ guint *corners)
+{
+ gfloat scale;
+ guint sum;
+
+ scale = 1.0;
+
+ /* top */
+ sum = node->border_radius[ST_CORNER_TOPLEFT]
+ + node->border_radius[ST_CORNER_TOPRIGHT];
+
+ if (sum > 0)
+ scale = MIN (width / sum, scale);
+
+ /* right */
+ sum = node->border_radius[ST_CORNER_TOPRIGHT]
+ + node->border_radius[ST_CORNER_BOTTOMRIGHT];
+
+ if (sum > 0)
+ scale = MIN (height / sum, scale);
+
+ /* bottom */
+ sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
+ + node->border_radius[ST_CORNER_BOTTOMRIGHT];
+
+ if (sum > 0)
+ scale = MIN (width / sum, scale);
+
+ /* left */
+ sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
+ + node->border_radius[ST_CORNER_TOPLEFT];
+
+ if (sum > 0)
+ scale = MIN (height / sum, scale);
+
+ corners[ST_CORNER_TOPLEFT] = node->border_radius[ST_CORNER_TOPLEFT] * scale;
+ corners[ST_CORNER_TOPRIGHT] = node->border_radius[ST_CORNER_TOPRIGHT] * scale;
+ corners[ST_CORNER_BOTTOMLEFT] = node->border_radius[ST_CORNER_BOTTOMLEFT] * scale;
+ corners[ST_CORNER_BOTTOMRIGHT] = node->border_radius[ST_CORNER_BOTTOMRIGHT] * scale;
+}
+
+static void
+st_theme_node_get_corner_border_widths (StThemeNode *node,
+ StCorner corner_id,
+ guint *border_width_1,
+ guint *border_width_2)
+{
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_TOP];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_LEFT];
+ break;
+ case ST_CORNER_TOPRIGHT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_TOP];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_RIGHT];
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_RIGHT];
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_LEFT];
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+static CoglPipeline *
+st_theme_node_lookup_corner (StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale,
+ StCorner corner_id)
+{
+ CoglTexture *texture = NULL;
+ CoglPipeline *material = NULL;
+ char *key;
+ StTextureCache *cache;
+ StCornerSpec corner;
+ guint radius[4];
+
+ cache = st_texture_cache_get_default ();
+
+ st_theme_node_reduce_border_radius (node, width, height, radius);
+
+ if (radius[corner_id] == 0)
+ return NULL;
+
+ corner.radius = radius[corner_id];
+ corner.color = node->background_color;
+ corner.resource_scale = resource_scale;
+ st_theme_node_get_corner_border_widths (node, corner_id,
+ &corner.border_width_1,
+ &corner.border_width_2);
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_TOPRIGHT:
+ over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ if (corner.color.alpha == 0 &&
+ corner.border_color_1.alpha == 0 &&
+ corner.border_color_2.alpha == 0)
+ {
+ if (node->box_shadow == NULL)
+ return NULL;
+ else /* We still need a corner texture to render the box-shadow */
+ corner.color = (ClutterColor) {0, 0, 0, 255};
+ }
+
+ key = corner_to_string (&corner);
+ texture = st_texture_cache_load (cache, key, ST_TEXTURE_CACHE_POLICY_FOREVER, load_corner, &corner, NULL);
+
+ if (texture)
+ {
+ material = _st_create_texture_pipeline (texture);
+ cogl_object_unref (texture);
+ }
+
+ g_free (key);
+
+ return material;
+}
+
+static void
+get_background_scale (StThemeNode *node,
+ gdouble painting_area_width,
+ gdouble painting_area_height,
+ gdouble background_image_width,
+ gdouble background_image_height,
+ gdouble *scale_w,
+ gdouble *scale_h)
+{
+ *scale_w = -1.0;
+ *scale_h = -1.0;
+
+ switch (node->background_size)
+ {
+ case ST_BACKGROUND_SIZE_AUTO:
+ *scale_w = 1.0f;
+ break;
+ case ST_BACKGROUND_SIZE_CONTAIN:
+ *scale_w = MIN (painting_area_width / background_image_width,
+ painting_area_height / background_image_height);
+ break;
+ case ST_BACKGROUND_SIZE_COVER:
+ *scale_w = MAX (painting_area_width / background_image_width,
+ painting_area_height / background_image_height);
+ break;
+ case ST_BACKGROUND_SIZE_FIXED:
+ if (node->background_size_w > -1)
+ {
+ *scale_w = node->background_size_w / background_image_width;
+ if (node->background_size_h > -1)
+ *scale_h = node->background_size_h / background_image_height;
+ }
+ else if (node->background_size_h > -1)
+ *scale_w = node->background_size_h / background_image_height;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ if (*scale_h < 0.0)
+ *scale_h = *scale_w;
+}
+
+static void
+get_background_coordinates (StThemeNode *node,
+ gdouble painting_area_width,
+ gdouble painting_area_height,
+ gdouble background_image_width,
+ gdouble background_image_height,
+ gdouble *x,
+ gdouble *y)
+{
+ /* honor the specified position if any */
+ if (node->background_position_set)
+ {
+ *x = node->background_position_x;
+ *y = node->background_position_y;
+ }
+ else
+ {
+ /* center the background on the widget */
+ *x = (painting_area_width / 2.0) - (background_image_width / 2.0);
+ *y = (painting_area_height / 2.0) - (background_image_height / 2.0);
+ }
+}
+
+static void
+get_background_position (StThemeNode *self,
+ const ClutterActorBox *allocation,
+ float resource_scale,
+ ClutterActorBox *result,
+ ClutterActorBox *texture_coords)
+{
+ gdouble painting_area_width, painting_area_height;
+ gdouble background_image_width, background_image_height;
+ gdouble x1, y1;
+ gdouble scale_w, scale_h;
+
+ /* get the background image size */
+ background_image_width = cogl_texture_get_width (self->background_texture);
+ background_image_height = cogl_texture_get_height (self->background_texture);
+
+ background_image_width /= resource_scale;
+ background_image_height /= resource_scale;
+
+ /* get the painting area size */
+ painting_area_width = allocation->x2 - allocation->x1;
+ painting_area_height = allocation->y2 - allocation->y1;
+
+ /* scale if requested */
+ get_background_scale (self,
+ painting_area_width, painting_area_height,
+ background_image_width, background_image_height,
+ &scale_w, &scale_h);
+
+ background_image_width *= scale_w;
+ background_image_height *= scale_h;
+
+ /* get coordinates */
+ get_background_coordinates (self,
+ painting_area_width, painting_area_height,
+ background_image_width, background_image_height,
+ &x1, &y1);
+
+ if (self->background_repeat)
+ {
+ gdouble width = allocation->x2 - allocation->x1 + x1;
+ gdouble height = allocation->y2 - allocation->y1 + y1;
+
+ *result = *allocation;
+
+ /* reference image is at x1, y1 */
+ texture_coords->x1 = x1 / background_image_width;
+ texture_coords->y1 = y1 / background_image_height;
+ texture_coords->x2 = width / background_image_width;
+ texture_coords->y2 = height / background_image_height;
+ }
+ else
+ {
+ result->x1 = x1;
+ result->y1 = y1;
+ result->x2 = x1 + background_image_width;
+ result->y2 = y1 + background_image_height;
+
+ texture_coords->x1 = texture_coords->y1 = 0;
+ texture_coords->x2 = texture_coords->y2 = 1;
+ }
+}
+
+/* Use of this function marks code which doesn't support
+ * non-uniform colors.
+ */
+static void
+get_arbitrary_border_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ if (color)
+ st_theme_node_get_border_color (node, ST_SIDE_TOP, color);
+}
+
+static gboolean
+st_theme_node_has_visible_outline (StThemeNode *node)
+{
+ if (node->background_color.alpha > 0)
+ return TRUE;
+
+ if (node->background_gradient_end.alpha > 0)
+ return TRUE;
+
+ if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
+ node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ return TRUE;
+
+ if (node->border_width[ST_SIDE_TOP] > 0 ||
+ node->border_width[ST_SIDE_LEFT] > 0 ||
+ node->border_width[ST_SIDE_RIGHT] > 0 ||
+ node->border_width[ST_SIDE_BOTTOM] > 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static cairo_pattern_t *
+create_cairo_pattern_of_background_gradient (StThemeNode *node,
+ float width,
+ float height)
+{
+ cairo_pattern_t *pattern;
+
+ g_return_val_if_fail (node->background_gradient_type != ST_GRADIENT_NONE,
+ NULL);
+
+ if (node->background_gradient_type == ST_GRADIENT_VERTICAL)
+ pattern = cairo_pattern_create_linear (0, 0, 0, height);
+ else if (node->background_gradient_type == ST_GRADIENT_HORIZONTAL)
+ pattern = cairo_pattern_create_linear (0, 0, width, 0);
+ else
+ {
+ gdouble cx, cy;
+
+ cx = width / 2.;
+ cy = height / 2.;
+ pattern = cairo_pattern_create_radial (cx, cy, 0, cx, cy, MIN (cx, cy));
+ }
+
+ cairo_pattern_add_color_stop_rgba (pattern, 0,
+ node->background_color.red / 255.,
+ node->background_color.green / 255.,
+ node->background_color.blue / 255.,
+ node->background_color.alpha / 255.);
+ cairo_pattern_add_color_stop_rgba (pattern, 1,
+ node->background_gradient_end.red / 255.,
+ node->background_gradient_end.green / 255.,
+ node->background_gradient_end.blue / 255.,
+ node->background_gradient_end.alpha / 255.);
+ return pattern;
+}
+
+static cairo_pattern_t *
+create_cairo_pattern_of_background_image (StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale,
+ gboolean *needs_background_fill)
+{
+ cairo_surface_t *surface;
+ cairo_pattern_t *pattern;
+ cairo_content_t content;
+ cairo_matrix_t matrix;
+ GFile *file;
+
+ StTextureCache *texture_cache;
+
+ gdouble background_image_width, background_image_height;
+ gdouble x, y;
+ gdouble scale_w, scale_h;
+
+ file = st_theme_node_get_background_image (node);
+
+ texture_cache = st_texture_cache_get_default ();
+
+ surface = st_texture_cache_load_file_to_cairo_surface (texture_cache, file,
+ node->cached_scale_factor,
+ resource_scale);
+
+ if (surface == NULL)
+ return NULL;
+
+ g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+
+ content = cairo_surface_get_content (surface);
+ pattern = cairo_pattern_create_for_surface (surface);
+
+ background_image_width = cairo_image_surface_get_width (surface);
+ background_image_height = cairo_image_surface_get_height (surface);
+
+ *needs_background_fill = TRUE;
+
+ cairo_matrix_init_identity (&matrix);
+
+ if (resource_scale != 1.0)
+ {
+ background_image_width /= resource_scale;
+ background_image_height /= resource_scale;
+
+ cairo_matrix_scale (&matrix, resource_scale, resource_scale);
+ }
+
+ get_background_scale (node,
+ width, height,
+ background_image_width, background_image_height,
+ &scale_w, &scale_h);
+
+ if ((scale_w != 1) || (scale_h != 1))
+ cairo_matrix_scale (&matrix, 1.0/scale_w, 1.0/scale_h);
+
+ background_image_width *= scale_w;
+ background_image_height *= scale_h;
+
+ get_background_coordinates (node,
+ width, height,
+ background_image_width, background_image_height,
+ &x, &y);
+ cairo_matrix_translate (&matrix, -x, -y);
+
+ if (node->background_repeat)
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+
+ /* If it's opaque, fills up the entire allocated
+ * area, then don't bother doing a background fill first
+ */
+ if (content != CAIRO_CONTENT_COLOR_ALPHA)
+ {
+ if (node->background_repeat ||
+ (x >= 0 &&
+ y >= 0 &&
+ background_image_width - x >= width &&
+ background_image_height -y >= height))
+ *needs_background_fill = FALSE;
+ }
+
+ cairo_pattern_set_matrix (pattern, &matrix);
+
+ return pattern;
+}
+
+/* fill_exterior = TRUE means that pattern is a surface pattern and
+ * we should extend the pattern with a solid fill from its edges.
+ * This is a bit of a hack; the alternative would be to make the
+ * surface of the surface pattern 1 pixel bigger and use CAIRO_EXTEND_PAD.
+ */
+static void
+paint_shadow_pattern_to_cairo_context (StShadow *shadow_spec,
+ cairo_pattern_t *pattern,
+ gboolean fill_exterior,
+ cairo_t *cr,
+ cairo_path_t *interior_path,
+ cairo_path_t *outline_path)
+{
+ /* If there are borders, clip the shadow to the interior
+ * of the borders; if there is a visible outline, clip the shadow to
+ * that outline
+ */
+ cairo_path_t *path = (interior_path != NULL) ? interior_path : outline_path;
+ double x1, x2, y1, y2;
+
+ /* fill_exterior only makes sense if we're clipping the shadow - filling
+ * to the edges of the surface would be silly */
+ g_assert (!(fill_exterior && path == NULL));
+
+ cairo_save (cr);
+ if (path != NULL)
+ {
+ cairo_append_path (cr, path);
+
+ /* There's no way to invert a path in cairo, so we need bounds for
+ * the area we are drawing in order to create the "exterior" region.
+ * Pixel align to hit fast paths.
+ */
+ if (fill_exterior)
+ {
+ cairo_path_extents (cr, &x1, &y1, &x2, &y2);
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+ }
+
+ cairo_clip (cr);
+ }
+
+ cairo_set_source_rgba (cr,
+ shadow_spec->color.red / 255.0,
+ shadow_spec->color.green / 255.0,
+ shadow_spec->color.blue / 255.0,
+ shadow_spec->color.alpha / 255.0);
+ if (fill_exterior)
+ {
+ cairo_surface_t *surface;
+ int width, height;
+ double xscale, yscale;
+ cairo_matrix_t matrix;
+
+ cairo_save (cr);
+
+ /* Start with a rectangle enclosing the bounds of the clipped
+ * region */
+ cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+
+ /* Then subtract out the bounds of the surface in the surface
+ * pattern; we transform the context by the inverse of the
+ * pattern matrix to get to surface coordinates */
+
+ if (cairo_pattern_get_surface (pattern, &surface) != CAIRO_STATUS_SUCCESS)
+ /* Something went wrong previously */
+ goto no_surface;
+
+ cairo_surface_get_device_scale (surface, &xscale, &yscale);
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+
+ cairo_pattern_get_matrix (pattern, &matrix);
+ cairo_matrix_invert (&matrix);
+ cairo_matrix_scale (&matrix, 1.0 / xscale, 1.0 / yscale);
+ cairo_transform (cr, &matrix);
+
+ cairo_rectangle (cr, 0, height, width, - height);
+ cairo_fill (cr);
+
+ no_surface:
+ cairo_restore (cr);
+ }
+
+ cairo_mask (cr, pattern);
+ cairo_restore (cr);
+}
+
+static void
+paint_background_image_shadow_to_cairo_context (StThemeNode *node,
+ StShadow *shadow_spec,
+ cairo_pattern_t *pattern,
+ cairo_t *cr,
+ cairo_path_t *interior_path,
+ cairo_path_t *outline_path,
+ int x,
+ int y,
+ int width,
+ int height,
+ float resource_scale)
+{
+ cairo_pattern_t *shadow_pattern;
+
+ g_assert (shadow_spec != NULL);
+ g_assert (pattern != NULL);
+
+ if (outline_path != NULL)
+ {
+ cairo_surface_t *clipped_surface;
+ cairo_pattern_t *clipped_pattern;
+ cairo_t *temp_cr;
+
+ /* Prerender the pattern to a temporary surface,
+ * so it's properly clipped before we create a shadow from it
+ */
+ width = ceilf (width * resource_scale);
+ height = ceilf (height * resource_scale);
+ clipped_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ cairo_surface_set_device_scale (clipped_surface, resource_scale, resource_scale);
+ temp_cr = cairo_create (clipped_surface);
+
+ cairo_set_operator (temp_cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (temp_cr);
+ cairo_set_operator (temp_cr, CAIRO_OPERATOR_SOURCE);
+
+ if (interior_path != NULL)
+ {
+ cairo_append_path (temp_cr, interior_path);
+ cairo_clip (temp_cr);
+ }
+
+ cairo_append_path (temp_cr, outline_path);
+ cairo_translate (temp_cr, x, y);
+ cairo_set_source (temp_cr, pattern);
+ cairo_clip (temp_cr);
+ cairo_paint (temp_cr);
+ cairo_destroy (temp_cr);
+
+ clipped_pattern = cairo_pattern_create_for_surface (clipped_surface);
+ cairo_surface_destroy (clipped_surface);
+
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
+ clipped_pattern);
+ cairo_pattern_destroy (clipped_pattern);
+ }
+ else
+ {
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
+ pattern);
+ }
+
+ paint_shadow_pattern_to_cairo_context (shadow_spec,
+ shadow_pattern, FALSE,
+ cr,
+ interior_path,
+ outline_path);
+ cairo_pattern_destroy (shadow_pattern);
+}
+
+/* gets the extents of a cairo_path_t; slightly inefficient, but much simpler than
+ * computing from the raw path data */
+static void
+path_extents (cairo_path_t *path,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+
+{
+ cairo_surface_t *dummy = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
+ cairo_t *cr = cairo_create (dummy);
+
+ cairo_append_path (cr, path);
+ cairo_path_extents (cr, x1, y1, x2, y2);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (dummy);
+}
+
+static void
+paint_inset_box_shadow_to_cairo_context (StThemeNode *node,
+ StShadow *shadow_spec,
+ float resource_scale,
+ cairo_t *cr,
+ cairo_path_t *shadow_outline)
+{
+ cairo_surface_t *shadow_surface;
+ cairo_pattern_t *shadow_pattern;
+ double extents_x1, extents_y1, extents_x2, extents_y2;
+ double shrunk_extents_x1, shrunk_extents_y1, shrunk_extents_x2, shrunk_extents_y2;
+ gboolean fill_exterior;
+
+ g_assert (shadow_spec != NULL);
+ g_assert (shadow_outline != NULL);
+
+ /* Create the pattern used to create the inset shadow; as the shadow
+ * should be drawn as if everything outside the outline was opaque,
+ * we use a temporary surface to draw the background as a solid shape,
+ * which is inverted when creating the shadow pattern.
+ */
+
+ /* First we need to find the size of the temporary surface
+ */
+ path_extents (shadow_outline,
+ &extents_x1, &extents_y1, &extents_x2, &extents_y2);
+
+ /* Shrink the extents by the spread, and offset */
+ shrunk_extents_x1 = extents_x1 + shadow_spec->xoffset + shadow_spec->spread;
+ shrunk_extents_y1 = extents_y1 + shadow_spec->yoffset + shadow_spec->spread;
+ shrunk_extents_x2 = extents_x2 + shadow_spec->xoffset - shadow_spec->spread;
+ shrunk_extents_y2 = extents_y2 + shadow_spec->yoffset - shadow_spec->spread;
+
+ if (shrunk_extents_x1 >= shrunk_extents_x2 || shrunk_extents_y1 >= shrunk_extents_y2)
+ {
+ /* Shadow occupies entire area within border */
+ shadow_pattern = cairo_pattern_create_rgb (0., 0., 0.);
+ fill_exterior = FALSE;
+ }
+ else
+ {
+ /* Bounds of temporary surface */
+ int surface_x = floor (shrunk_extents_x1);
+ int surface_y = floor (shrunk_extents_y1);
+ int surface_width = ceil ((shrunk_extents_x2 - surface_x) * resource_scale);
+ int surface_height = ceil ((shrunk_extents_y2 - surface_y) * resource_scale);
+
+ /* Center of the original path */
+ double x_center = (extents_x1 + extents_x2) / 2;
+ double y_center = (extents_y1 + extents_y2) / 2;
+
+ cairo_pattern_t *pattern;
+ cairo_t *temp_cr;
+ cairo_matrix_t matrix;
+
+ shadow_surface = cairo_image_surface_create (CAIRO_FORMAT_A8, surface_width, surface_height);
+ cairo_surface_set_device_scale (shadow_surface, resource_scale, resource_scale);
+ temp_cr = cairo_create (shadow_surface);
+
+ /* Match the coordinates in the temporary context to the parent context */
+ cairo_translate (temp_cr, - surface_x, - surface_y);
+
+ /* Shadow offset */
+ cairo_translate (temp_cr, shadow_spec->xoffset, shadow_spec->yoffset);
+
+ /* Scale the path around the center to match the shrunk bounds */
+ cairo_translate (temp_cr, x_center, y_center);
+ cairo_scale (temp_cr,
+ (shrunk_extents_x2 - shrunk_extents_x1) / (extents_x2 - extents_x1),
+ (shrunk_extents_y2 - shrunk_extents_y1) / (extents_y2 - extents_y1));
+ cairo_translate (temp_cr, - x_center, - y_center);
+
+ cairo_append_path (temp_cr, shadow_outline);
+ cairo_fill (temp_cr);
+ cairo_destroy (temp_cr);
+
+ pattern = cairo_pattern_create_for_surface (shadow_surface);
+ cairo_surface_destroy (shadow_surface);
+
+ /* The pattern needs to be offset back to coordinates in the parent context */
+ cairo_matrix_init_translate (&matrix, - surface_x, - surface_y);
+ cairo_pattern_set_matrix (pattern, &matrix);
+
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, pattern);
+ fill_exterior = TRUE;
+
+ cairo_pattern_destroy (pattern);
+ }
+
+ paint_shadow_pattern_to_cairo_context (shadow_spec,
+ shadow_pattern, fill_exterior,
+ cr,
+ shadow_outline,
+ NULL);
+
+ cairo_pattern_destroy (shadow_pattern);
+}
+
+/* In order for borders to be smoothly blended with non-solid backgrounds,
+ * we need to use cairo. This function is a slow fallback path for those
+ * cases (gradients, background images, etc).
+ */
+static CoglTexture *
+st_theme_node_prerender_background (StThemeNode *node,
+ float actor_width,
+ float actor_height,
+ float resource_scale)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ GError *error = NULL;
+ StBorderImage *border_image;
+ CoglTexture *texture;
+ guint radius[4];
+ int i;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ StShadow *shadow_spec;
+ StShadow *box_shadow_spec;
+ cairo_pattern_t *pattern = NULL;
+ cairo_path_t *outline_path = NULL;
+ gboolean draw_solid_background = TRUE;
+ gboolean background_is_translucent;
+ gboolean interior_dirty;
+ gboolean draw_background_image_shadow = FALSE;
+ gboolean has_visible_outline;
+ ClutterColor border_color;
+ guint border_width[4];
+ guint rowstride;
+ guchar *data;
+ ClutterActorBox actor_box;
+ ClutterActorBox paint_box;
+ cairo_path_t *interior_path = NULL;
+ float width, height;
+ int texture_width;
+ int texture_height;
+
+ border_image = st_theme_node_get_border_image (node);
+
+ shadow_spec = st_theme_node_get_background_image_shadow (node);
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ actor_box.x1 = 0;
+ actor_box.x2 = actor_width;
+ actor_box.y1 = 0;
+ actor_box.y2 = actor_height;
+
+ /* If there's a background image shadow, we
+ * may need to create an image bigger than the nodes
+ * allocation
+ */
+ st_theme_node_get_background_paint_box (node, &actor_box, &paint_box);
+
+ /* translate the boxes so the paint box is at 0,0
+ */
+ actor_box.x1 += - paint_box.x1;
+ actor_box.x2 += - paint_box.x1;
+ actor_box.y1 += - paint_box.y1;
+ actor_box.y2 += - paint_box.y1;
+
+ width = paint_box.x2 - paint_box.x1;
+ height = paint_box.y2 - paint_box.y1;
+
+ texture_width = ceilf (width * resource_scale);
+ texture_height = ceilf (height * resource_scale);
+
+ rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, texture_width);
+ data = g_new0 (guchar, texture_height * rowstride);
+
+ /* We zero initialize the destination memory, so it's fully transparent
+ * by default.
+ */
+ interior_dirty = FALSE;
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ texture_width, texture_height,
+ rowstride);
+ cairo_surface_set_device_scale (surface, resource_scale, resource_scale);
+ cr = cairo_create (surface);
+
+ /* TODO - support non-uniform border colors */
+ get_arbitrary_border_color (node, &border_color);
+
+ st_theme_node_reduce_border_radius (node, width, height, radius);
+
+ for (i = 0; i < 4; i++)
+ border_width[i] = st_theme_node_get_border_width (node, i);
+
+ /* Note we don't support translucent background images on top
+ * of gradients. It's strictly either/or.
+ */
+ if (node->background_gradient_type != ST_GRADIENT_NONE)
+ {
+ pattern = create_cairo_pattern_of_background_gradient (node, width, height);
+ draw_solid_background = FALSE;
+
+ /* If the gradient has any translucent areas, we need to
+ * erase the interior region before drawing, so that we show
+ * what's actually under the gradient and not whatever is
+ * left over from filling the border, etc.
+ */
+ if (node->background_color.alpha < 255 ||
+ node->background_gradient_end.alpha < 255)
+ background_is_translucent = TRUE;
+ else
+ background_is_translucent = FALSE;
+ }
+ else
+ {
+ GFile *background_image;
+
+ background_image = st_theme_node_get_background_image (node);
+
+ if (background_image != NULL)
+ {
+ pattern = create_cairo_pattern_of_background_image (node,
+ width, height,
+ resource_scale,
+ &draw_solid_background);
+ if (shadow_spec && pattern != NULL)
+ draw_background_image_shadow = TRUE;
+ }
+
+ /* We never need to clear the interior region before drawing the
+ * background image, because it either always fills the entire area
+ * opaquely, or we draw the solid background behind it.
+ */
+ background_is_translucent = FALSE;
+ }
+
+ if (pattern == NULL)
+ draw_solid_background = TRUE;
+
+ /* drawing the solid background implicitly clears the interior
+ * region, so if we're going to draw a solid background before drawing
+ * the background pattern, then we don't need to bother also clearing the
+ * background region.
+ */
+ if (draw_solid_background)
+ background_is_translucent = FALSE;
+
+ has_visible_outline = st_theme_node_has_visible_outline (node);
+
+ /* Create a path for the background's outline first */
+ if (radius[ST_CORNER_TOPLEFT] > 0)
+ cairo_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_TOPLEFT],
+ actor_box.y1 + radius[ST_CORNER_TOPLEFT],
+ radius[ST_CORNER_TOPLEFT], M_PI, 3 * M_PI / 2);
+ else
+ cairo_move_to (cr, actor_box.x1, actor_box.y1);
+ cairo_line_to (cr, actor_box.x2 - radius[ST_CORNER_TOPRIGHT], actor_box.x1);
+ if (radius[ST_CORNER_TOPRIGHT] > 0)
+ cairo_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
+ actor_box.x1 + radius[ST_CORNER_TOPRIGHT],
+ radius[ST_CORNER_TOPRIGHT], 3 * M_PI / 2, 2 * M_PI);
+ cairo_line_to (cr, actor_box.x2, actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT]);
+ if (radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ cairo_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT], 0, M_PI / 2);
+ cairo_line_to (cr, actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], actor_box.y2);
+ if (radius[ST_CORNER_BOTTOMLEFT] > 0)
+ cairo_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
+ radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI);
+ cairo_close_path (cr);
+
+ outline_path = cairo_copy_path (cr);
+
+ /* If we have a solid border, we fill the outline shape with the border
+ * color and create the inline shape for the background;
+ * otherwise the outline shape is filled with the background
+ * directly
+ */
+ if (border_image == NULL &&
+ (border_width[ST_SIDE_TOP] > 0 ||
+ border_width[ST_SIDE_RIGHT] > 0 ||
+ border_width[ST_SIDE_BOTTOM] > 0 ||
+ border_width[ST_SIDE_LEFT] > 0))
+ {
+ cairo_set_source_rgba (cr,
+ border_color.red / 255.,
+ border_color.green / 255.,
+ border_color.blue / 255.,
+ border_color.alpha / 255.);
+ cairo_fill (cr);
+
+ /* We were sloppy when filling in the border, and now the interior
+ * is filled with the border color, too.
+ */
+ interior_dirty = TRUE;
+
+ if (radius[ST_CORNER_TOPLEFT] > MAX(border_width[ST_SIDE_TOP],
+ border_width[ST_SIDE_LEFT]))
+ elliptical_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_TOPLEFT],
+ actor_box.y1 + radius[ST_CORNER_TOPLEFT],
+ radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_LEFT],
+ radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_TOP],
+ M_PI, 3 * M_PI / 2);
+ else
+ cairo_move_to (cr,
+ actor_box.x1 + border_width[ST_SIDE_LEFT],
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ cairo_line_to (cr,
+ actor_box.x2 - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]),
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ if (radius[ST_CORNER_TOPRIGHT] > MAX(border_width[ST_SIDE_TOP],
+ border_width[ST_SIDE_RIGHT]))
+ elliptical_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
+ actor_box.y1 + radius[ST_CORNER_TOPRIGHT],
+ radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_RIGHT],
+ radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_TOP],
+ 3 * M_PI / 2, 2 * M_PI);
+ else
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y2 - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM]));
+
+ if (radius[ST_CORNER_BOTTOMRIGHT] > MAX(border_width[ST_SIDE_BOTTOM],
+ border_width[ST_SIDE_RIGHT]))
+ elliptical_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_RIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_BOTTOM],
+ 0, M_PI / 2);
+ else
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ cairo_line_to (cr,
+ MAX(radius[ST_CORNER_BOTTOMLEFT], border_width[ST_SIDE_LEFT]),
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ if (radius[ST_CORNER_BOTTOMLEFT] > MAX(border_width[ST_SIDE_BOTTOM],
+ border_width[ST_SIDE_LEFT]))
+ elliptical_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
+ radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_LEFT],
+ radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_BOTTOM],
+ M_PI / 2, M_PI);
+ else
+ cairo_line_to (cr,
+ actor_box.x1 + border_width[ST_SIDE_LEFT],
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ cairo_close_path (cr);
+
+ interior_path = cairo_copy_path (cr);
+
+ /* clip drawing to the region inside of the borders
+ */
+ cairo_clip (cr);
+
+ /* But fill the pattern as if it started at the edge of outline,
+ * behind the borders. This is similar to
+ * background-clip: border-box; semantics.
+ */
+ cairo_append_path (cr, outline_path);
+ }
+
+ if (interior_dirty && background_is_translucent)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_fill_preserve (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ }
+
+ if (draw_solid_background)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ cairo_set_source_rgba (cr,
+ node->background_color.red / 255.,
+ node->background_color.green / 255.,
+ node->background_color.blue / 255.,
+ node->background_color.alpha / 255.);
+ cairo_fill_preserve (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ }
+
+ if (draw_background_image_shadow)
+ {
+ paint_background_image_shadow_to_cairo_context (node,
+ shadow_spec,
+ pattern,
+ cr,
+ interior_path,
+ has_visible_outline? outline_path : NULL,
+ actor_box.x1,
+ actor_box.y1,
+ width, height,
+ resource_scale);
+ cairo_append_path (cr, outline_path);
+ }
+
+ cairo_translate (cr, actor_box.x1, actor_box.y1);
+
+ if (pattern != NULL)
+ {
+ cairo_set_source (cr, pattern);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pattern);
+ }
+
+ if (box_shadow_spec && box_shadow_spec->inset)
+ {
+ paint_inset_box_shadow_to_cairo_context (node,
+ box_shadow_spec,
+ resource_scale,
+ cr,
+ interior_path ? interior_path
+ : outline_path);
+ }
+
+ if (outline_path != NULL)
+ cairo_path_destroy (outline_path);
+
+ if (interior_path != NULL)
+ cairo_path_destroy (interior_path);
+
+ texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx,
+ texture_width,
+ texture_height,
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ rowstride,
+ data,
+ &error));
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_error_free (error);
+ }
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ g_free (data);
+
+ return texture;
+}
+
+static void st_theme_node_paint_borders (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ StPaintBordersMode mode,
+ guint8 paint_opacity);
+
+void
+st_theme_node_invalidate_border_image (StThemeNode *node)
+{
+ cogl_clear_object (&node->border_slices_texture);
+ cogl_clear_object (&node->border_slices_pipeline);
+}
+
+static gboolean
+st_theme_node_load_border_image (StThemeNode *node,
+ gfloat resource_scale)
+{
+ if (node->border_slices_texture == NULL)
+ {
+ StBorderImage *border_image;
+ GFile *file;
+
+ border_image = st_theme_node_get_border_image (node);
+ if (border_image == NULL)
+ goto out;
+
+ file = st_border_image_get_file (border_image);
+
+ node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (st_texture_cache_get_default (),
+ file,
+ node->cached_scale_factor,
+ resource_scale);
+ if (node->border_slices_texture == NULL)
+ goto out;
+
+ node->border_slices_pipeline = _st_create_texture_pipeline (node->border_slices_texture);
+ }
+
+ out:
+ return node->border_slices_texture != NULL;
+}
+
+void
+st_theme_node_invalidate_background_image (StThemeNode *node)
+{
+ cogl_clear_object (&node->background_texture);
+ cogl_clear_object (&node->background_pipeline);
+ cogl_clear_object (&node->background_shadow_pipeline);
+}
+
+static gboolean
+st_theme_node_load_background_image (StThemeNode *node,
+ gfloat resource_scale)
+{
+ if (node->background_texture == NULL)
+ {
+ GFile *background_image;
+ StShadow *background_image_shadow_spec;
+
+ background_image = st_theme_node_get_background_image (node);
+ if (background_image == NULL)
+ goto out;
+
+ background_image_shadow_spec = st_theme_node_get_background_image_shadow (node);
+ node->background_texture = st_texture_cache_load_file_to_cogl_texture (st_texture_cache_get_default (),
+ background_image,
+ node->cached_scale_factor,
+ resource_scale);
+ if (node->background_texture == NULL)
+ goto out;
+
+ node->background_pipeline = _st_create_texture_pipeline (node->background_texture);
+
+ if (node->background_repeat)
+ cogl_pipeline_set_layer_wrap_mode (node->background_pipeline, 0,
+ COGL_PIPELINE_WRAP_MODE_REPEAT);
+
+ if (background_image_shadow_spec)
+ {
+ node->background_shadow_pipeline = _st_create_shadow_pipeline (background_image_shadow_spec,
+ node->background_texture,
+ resource_scale);
+ }
+ }
+
+ out:
+ return node->background_texture != NULL;
+}
+
+static gboolean
+st_theme_node_invalidate_resources_for_file (StThemeNode *node,
+ GFile *file)
+{
+ StBorderImage *border_image;
+ gboolean changed = FALSE;
+ GFile *theme_file;
+
+ theme_file = st_theme_node_get_background_image (node);
+ if ((theme_file != NULL) && g_file_equal (theme_file, file))
+ {
+ st_theme_node_invalidate_background_image (node);
+ changed = TRUE;
+ }
+
+ border_image = st_theme_node_get_border_image (node);
+ theme_file = border_image ? st_border_image_get_file (border_image) : NULL;
+ if ((theme_file != NULL) && g_file_equal (theme_file, file))
+ {
+ st_theme_node_invalidate_border_image (node);
+ changed = TRUE;
+ }
+
+ return changed;
+}
+
+static void st_theme_node_compute_maximum_borders (StThemeNodePaintState *state);
+static void st_theme_node_prerender_shadow (StThemeNodePaintState *state);
+
+static void
+st_theme_node_render_resources (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ gboolean has_border;
+ gboolean has_border_radius;
+ gboolean has_inset_box_shadow;
+ gboolean has_large_corners;
+ StShadow *box_shadow_spec;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ /* FIXME - need to separate this into things that need to be recomputed on
+ * geometry change versus things that can be cached regardless, such as
+ * a background image.
+ */
+ st_theme_node_paint_state_free (state);
+
+ st_theme_node_paint_state_set_node (state, node);
+ state->alloc_width = width;
+ state->alloc_height = height;
+ state->resource_scale = resource_scale;
+
+ _st_theme_node_ensure_background (node);
+ _st_theme_node_ensure_geometry (node);
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+ has_inset_box_shadow = box_shadow_spec && box_shadow_spec->inset;
+
+ if (node->border_width[ST_SIDE_TOP] > 0 ||
+ node->border_width[ST_SIDE_LEFT] > 0 ||
+ node->border_width[ST_SIDE_RIGHT] > 0 ||
+ node->border_width[ST_SIDE_BOTTOM] > 0)
+ has_border = TRUE;
+ else
+ has_border = FALSE;
+
+ if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
+ node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ has_border_radius = TRUE;
+ else
+ has_border_radius = FALSE;
+
+ /* The cogl code pads each corner to the maximum border radius,
+ * which results in overlapping corner areas if the radius
+ * exceeds the actor's halfsize, causing rendering errors.
+ * Fall back to cairo in these cases. */
+ has_large_corners = FALSE;
+
+ if (has_border_radius) {
+ guint border_radius[4];
+ int corner;
+
+ st_theme_node_reduce_border_radius (node, width, height, border_radius);
+
+ for (corner = 0; corner < 4; corner ++) {
+ if (border_radius[corner] * 2 > height ||
+ border_radius[corner] * 2 > width) {
+ has_large_corners = TRUE;
+ break;
+ }
+ }
+ }
+
+ state->corner_material[ST_CORNER_TOPLEFT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPLEFT);
+ state->corner_material[ST_CORNER_TOPRIGHT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPRIGHT);
+ state->corner_material[ST_CORNER_BOTTOMRIGHT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMRIGHT);
+ state->corner_material[ST_CORNER_BOTTOMLEFT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMLEFT);
+
+ /* Use cairo to prerender the node if there is a gradient, or
+ * background image with borders and/or rounded corners,
+ * or large corners, since we can't do those things
+ * easily with cogl.
+ *
+ * FIXME: if we could figure out ahead of time that a
+ * background image won't overlap with the node borders,
+ * then we could use cogl for that case.
+ */
+ if ((node->background_gradient_type != ST_GRADIENT_NONE)
+ || (has_inset_box_shadow && (has_border || node->background_color.alpha > 0))
+ || (st_theme_node_get_background_image (node) && (has_border || has_border_radius))
+ || has_large_corners)
+ state->prerendered_texture = st_theme_node_prerender_background (node, width, height,
+ resource_scale);
+
+ if (state->prerendered_texture)
+ state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
+ else
+ state->prerendered_pipeline = NULL;
+
+ if (box_shadow_spec && !has_inset_box_shadow)
+ {
+ st_theme_node_compute_maximum_borders (state);
+
+ if (st_theme_node_load_border_image (node, resource_scale))
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ node->border_slices_texture,
+ state->resource_scale);
+ else if (state->prerendered_texture != NULL)
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ state->prerendered_texture,
+ state->resource_scale);
+ else
+ st_theme_node_prerender_shadow (state);
+ }
+
+ /* If we don't have cached textures yet, check whether we can cache
+ them. */
+ if (!node->cached_textures)
+ {
+ if (state->prerendered_pipeline == NULL &&
+ width >= node->box_shadow_min_width &&
+ height >= node->box_shadow_min_height)
+ {
+ st_theme_node_paint_state_copy (&node->cached_state, state);
+ node->cached_textures = TRUE;
+ }
+ }
+}
+
+static void
+st_theme_node_update_resources (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ gboolean had_prerendered_texture = FALSE;
+ gboolean had_box_shadow = FALSE;
+ StShadow *box_shadow_spec;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ /* Free handles we can't reuse */
+ had_prerendered_texture = (state->prerendered_texture != NULL);
+ cogl_clear_object (&state->prerendered_texture);
+
+ if (state->prerendered_pipeline != NULL)
+ {
+ cogl_clear_object (&state->prerendered_pipeline);
+
+ if (node->border_slices_texture == NULL &&
+ state->box_shadow_pipeline != NULL)
+ {
+ cogl_clear_object (&state->box_shadow_pipeline);
+ had_box_shadow = TRUE;
+ }
+ }
+
+ st_theme_node_paint_state_set_node (state, node);
+ state->alloc_width = width;
+ state->alloc_height = height;
+ state->resource_scale = resource_scale;
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ if (had_prerendered_texture)
+ {
+ state->prerendered_texture = st_theme_node_prerender_background (node, width, height, resource_scale);
+ state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
+ }
+ else
+ {
+ int corner_id;
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ if (state->corner_material[corner_id] == NULL)
+ state->corner_material[corner_id] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, corner_id);
+ }
+
+ if (had_box_shadow)
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ state->prerendered_texture,
+ state->resource_scale);
+}
+
+static void
+paint_material_with_opacity (CoglPipeline *material,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *box,
+ ClutterActorBox *coords,
+ guint8 paint_opacity)
+{
+ cogl_pipeline_set_color4ub (material,
+ paint_opacity, paint_opacity, paint_opacity, paint_opacity);
+
+ if (coords)
+ cogl_framebuffer_draw_textured_rectangle (framebuffer, material,
+ box->x1, box->y1, box->x2, box->y2,
+ coords->x1, coords->y1, coords->x2, coords->y2);
+ else
+ cogl_framebuffer_draw_rectangle (framebuffer, material,
+ box->x1, box->y1, box->x2, box->y2);
+}
+
+static void
+st_theme_node_ensure_color_pipeline (StThemeNode *node)
+{
+ static CoglPipeline *color_pipeline_template = NULL;
+
+ if (node->color_pipeline != NULL)
+ return;
+
+ if (G_UNLIKELY (color_pipeline_template == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ color_pipeline_template = cogl_pipeline_new (ctx);
+ }
+
+ node->color_pipeline = cogl_pipeline_copy (color_pipeline_template);
+}
+
+static void
+st_theme_node_paint_borders (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ StPaintBordersMode mode,
+ guint8 paint_opacity)
+{
+ StThemeNode *node = state->node;
+ float width, height;
+ guint border_width[4];
+ guint border_radius[4];
+ guint max_border_radius = 0;
+ guint max_width_radius[4];
+ int corner_id, side_id;
+ ClutterColor border_color;
+ guint8 alpha;
+ gboolean corners_are_transparent;
+
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+
+ /* TODO - support non-uniform border colors */
+ get_arbitrary_border_color (node, &border_color);
+
+ for (side_id = 0; side_id < 4; side_id++)
+ border_width[side_id] = st_theme_node_get_border_width(node, side_id);
+
+ st_theme_node_reduce_border_radius (node, width, height, border_radius);
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ guint border_width_1, border_width_2;
+
+ st_theme_node_get_corner_border_widths (node, corner_id,
+ &border_width_1, &border_width_2);
+
+ if (border_radius[corner_id] > max_border_radius)
+ max_border_radius = border_radius[corner_id];
+ max_width_radius[corner_id] = MAX(MAX(border_width_1, border_width_2),
+ border_radius[corner_id]);
+ }
+
+ /* borders */
+ if (border_width[ST_SIDE_TOP] > 0 ||
+ border_width[ST_SIDE_RIGHT] > 0 ||
+ border_width[ST_SIDE_BOTTOM] > 0 ||
+ border_width[ST_SIDE_LEFT] > 0)
+ {
+ ClutterColor effective_border;
+ gboolean skip_corner_1, skip_corner_2;
+ float rects[16];
+
+ over (&border_color, &node->background_color, &effective_border);
+ alpha = paint_opacity * effective_border.alpha / 255;
+
+ if (alpha > 0)
+ {
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ effective_border.red * alpha / 255,
+ effective_border.green * alpha / 255,
+ effective_border.blue * alpha / 255,
+ alpha);
+
+ /* NORTH */
+ skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_TOPRIGHT] > 0;
+
+ rects[0] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT] : 0;
+ rects[1] = 0;
+ rects[2] = skip_corner_2 ? width - max_width_radius[ST_CORNER_TOPRIGHT] : width;
+ rects[3] = border_width[ST_SIDE_TOP];
+
+ /* EAST */
+ skip_corner_1 = border_radius[ST_CORNER_TOPRIGHT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
+
+ rects[4] = width - border_width[ST_SIDE_RIGHT];
+ rects[5] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPRIGHT]
+ : border_width[ST_SIDE_TOP];
+ rects[6] = width;
+ rects[7] = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMRIGHT]
+ : height - border_width[ST_SIDE_BOTTOM];
+
+ /* SOUTH */
+ skip_corner_1 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
+
+ rects[8] = skip_corner_1 ? max_width_radius[ST_CORNER_BOTTOMLEFT] : 0;
+ rects[9] = height - border_width[ST_SIDE_BOTTOM];
+ rects[10] = skip_corner_2 ? width - max_width_radius[ST_CORNER_BOTTOMRIGHT]
+ : width;
+ rects[11] = height;
+
+ /* WEST */
+ skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
+
+ rects[12] = 0;
+ rects[13] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT]
+ : border_width[ST_SIDE_TOP];
+ rects[14] = border_width[ST_SIDE_LEFT];
+ rects[15] = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMLEFT]
+ : height - border_width[ST_SIDE_BOTTOM];
+
+ cogl_framebuffer_draw_rectangles (framebuffer,
+ node->color_pipeline,
+ rects, 4);
+ }
+ }
+
+ corners_are_transparent = mode == ST_PAINT_BORDERS_MODE_COLOR &&
+ node->background_color.alpha == 0 &&
+ node->border_color[0].alpha == 0;
+
+ /* corners */
+ if (max_border_radius > 0 && paint_opacity > 0 && !corners_are_transparent)
+ {
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ if (state->corner_material[corner_id] == NULL)
+ continue;
+
+ cogl_pipeline_set_color4ub (state->corner_material[corner_id],
+ paint_opacity, paint_opacity,
+ paint_opacity, paint_opacity);
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id], 0, 0,
+ max_width_radius[ST_CORNER_TOPLEFT], max_width_radius[ST_CORNER_TOPLEFT],
+ 0, 0, 0.5, 0.5);
+ break;
+ case ST_CORNER_TOPRIGHT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ width - max_width_radius[ST_CORNER_TOPRIGHT], 0,
+ width, max_width_radius[ST_CORNER_TOPRIGHT],
+ 0.5, 0, 1, 0.5);
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ width - max_width_radius[ST_CORNER_BOTTOMRIGHT],
+ height - max_width_radius[ST_CORNER_BOTTOMRIGHT],
+ width, height,
+ 0.5, 0.5, 1, 1);
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ 0, height - max_width_radius[ST_CORNER_BOTTOMLEFT],
+ max_width_radius[ST_CORNER_BOTTOMLEFT], height,
+ 0, 0.5, 0.5, 1);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ }
+ }
+
+ /* background color */
+ alpha = mode == ST_PAINT_BORDERS_MODE_SILHOUETTE ?
+ 255 :
+ paint_opacity * node->background_color.alpha / 255;
+ if (alpha > 0)
+ {
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ node->background_color.red * alpha / 255,
+ node->background_color.green * alpha / 255,
+ node->background_color.blue * alpha / 255,
+ alpha);
+
+ /* We add padding to each corner, so that all corners end up as if they
+ * had a border-radius of max_border_radius, which allows us to treat
+ * corners as uniform further on.
+ */
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ float verts[8];
+ int n_rects;
+
+ /* corner texture does not need padding */
+ if (max_border_radius == border_radius[corner_id])
+ continue;
+
+ n_rects = border_radius[corner_id] == 0 ? 1 : 2;
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ verts[0] = border_width[ST_SIDE_LEFT];
+ verts[1] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ verts[2] = max_border_radius;
+ verts[3] = max_border_radius;
+ if (n_rects == 2)
+ {
+ verts[4] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_LEFT]);
+ verts[5] = border_width[ST_SIDE_TOP];
+ verts[6] = max_border_radius;
+ verts[7] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ }
+ break;
+ case ST_CORNER_TOPRIGHT:
+ verts[0] = width - max_border_radius;
+ verts[1] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ verts[2] = width - border_width[ST_SIDE_RIGHT];
+ verts[3] = max_border_radius;
+ if (n_rects == 2)
+ {
+ verts[4] = width - max_border_radius;
+ verts[5] = border_width[ST_SIDE_TOP];
+ verts[6] = width - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_RIGHT]);
+ verts[7] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ }
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ verts[0] = width - max_border_radius;
+ verts[1] = height - max_border_radius;
+ verts[2] = width - border_width[ST_SIDE_RIGHT];
+ verts[3] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ if (n_rects == 2)
+ {
+ verts[4] = width - max_border_radius;
+ verts[5] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ verts[6] = width - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_RIGHT]);
+ verts[7] = height - border_width[ST_SIDE_BOTTOM];
+ }
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ verts[0] = border_width[ST_SIDE_LEFT];
+ verts[1] = height - max_border_radius;
+ verts[2] = max_border_radius;
+ verts[3] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ if (n_rects == 2)
+ {
+ verts[4] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_LEFT]);
+ verts[5] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ verts[6] = max_border_radius;
+ verts[7] = height - border_width[ST_SIDE_BOTTOM];
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ cogl_framebuffer_draw_rectangles (framebuffer,
+ node->color_pipeline,
+ verts, n_rects);
+ }
+
+ /* Once we've drawn the borders and corners, if the corners are bigger
+ * then the border width, the remaining area is shaped like
+ *
+ * ########
+ * ##########
+ * ##########
+ * ########
+ *
+ * We draw it in at most 3 pieces - first the top and bottom if
+ * necessary, then the main rectangle
+ */
+ if (max_border_radius > border_width[ST_SIDE_TOP])
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
+ border_width[ST_SIDE_TOP],
+ width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
+ max_border_radius);
+ if (max_border_radius > border_width[ST_SIDE_BOTTOM])
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
+ height - max_border_radius,
+ width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
+ height - border_width[ST_SIDE_BOTTOM]);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ border_width[ST_SIDE_LEFT],
+ MAX(border_width[ST_SIDE_TOP], max_border_radius),
+ width - border_width[ST_SIDE_RIGHT],
+ height - MAX(border_width[ST_SIDE_BOTTOM], max_border_radius));
+ }
+}
+
+static void
+st_theme_node_paint_sliced_shadow (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity)
+{
+ StThemeNode *node = state->node;
+ guint border_radius[4];
+ CoglColor color;
+ StShadow *box_shadow_spec;
+ gfloat xoffset, yoffset;
+ gfloat width, height;
+ gfloat shadow_width, shadow_height;
+ gfloat xend, yend, top, bottom, left, right;
+ gfloat s_top, s_bottom, s_left, s_right;
+ gfloat shadow_blur_radius, x_spread_factor, y_spread_factor;
+ float rectangles[8 * 9];
+ gint idx;
+ ClutterColor background_color;
+ static const ClutterColor invisible_occluded = {3, 2, 1, 0};
+
+ if (paint_opacity == 0)
+ return;
+
+ st_theme_node_reduce_border_radius (node, box->x2 - box->x1, box->y2 - box->y1, border_radius);
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ /* Compute input & output areas :
+ *
+ * yoffset ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * top ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * bottom ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * yend ----------------------------
+ * xoffset left right xend
+ *
+ * s_top = top in offscreen's coordinates (0.0 - 1.0)
+ * s_bottom = bottom in offscreen's coordinates (0.0 - 1.0)
+ * s_left = left in offscreen's coordinates (0.0 - 1.0)
+ * s_right = right in offscreen's coordinates (0.0 - 1.0)
+ */
+ if (box_shadow_spec->blur == 0)
+ shadow_blur_radius = 0;
+ else
+ shadow_blur_radius = ceilf (1.5 * box_shadow_spec->blur / 2.0) * 2.0;
+
+ shadow_width = state->box_shadow_width + 2 * shadow_blur_radius;
+ shadow_height = state->box_shadow_height + 2 * shadow_blur_radius;
+
+ /* Compute input regions parameters */
+ s_top = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_TOPRIGHT]);
+ s_bottom = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_BOTTOMLEFT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+ s_left = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_BOTTOMLEFT]);
+ s_right = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPRIGHT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+
+ /* Compute output regions parameters */
+ xoffset = box->x1 + box_shadow_spec->xoffset - shadow_blur_radius - box_shadow_spec->spread;
+ yoffset = box->y1 + box_shadow_spec->yoffset - shadow_blur_radius - box_shadow_spec->spread;
+ width = box->x2 - box->x1 + 2 * shadow_blur_radius;
+ height = box->y2 - box->y1 + 2 * shadow_blur_radius;
+
+ x_spread_factor = (width + 2 * box_shadow_spec->spread) / width;
+ y_spread_factor = (height + 2 * box_shadow_spec->spread) / height;
+
+ width += 2 * box_shadow_spec->spread;
+ height += 2 * box_shadow_spec->spread;
+
+ xend = xoffset + width;
+ yend = yoffset + height;
+
+ top = s_top * y_spread_factor;
+ bottom = s_bottom * y_spread_factor;
+ left = s_left * x_spread_factor;
+ right = s_right * x_spread_factor;
+
+ bottom = height - bottom;
+ right = width - right;
+
+ /* Final adjustments */
+ s_top /= shadow_height;
+ s_bottom /= shadow_height;
+ s_left /= shadow_width;
+ s_right /= shadow_width;
+
+ s_bottom = 1.0 - s_bottom;
+ s_right = 1.0 - s_right;
+
+ top += yoffset;
+ bottom += yoffset;
+ left += xoffset;
+ right += xoffset;
+
+ /* Setup pipeline */
+ cogl_color_init_from_4ub (&color,
+ box_shadow_spec->color.red * paint_opacity / 255,
+ box_shadow_spec->color.green * paint_opacity / 255,
+ box_shadow_spec->color.blue * paint_opacity / 255,
+ box_shadow_spec->color.alpha * paint_opacity / 255);
+ cogl_color_premultiply (&color);
+
+ cogl_pipeline_set_layer_combine_constant (state->box_shadow_pipeline, 0, &color);
+
+ idx = 0;
+
+ if (yoffset < top)
+ {
+ if (xoffset < left)
+ {
+ /* Top left corner */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = left;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_top;
+ }
+
+ /* Top middle */
+ rectangles[idx++] = left;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = right;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_top;
+
+ if (xend > right)
+ {
+ /* Top right corner */
+ rectangles[idx++] = right;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = s_top;
+ }
+ }
+
+ if (xoffset < left)
+ {
+ /* Left middle */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = top;
+ rectangles[idx++] = left;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_bottom;
+ }
+
+ /* Center middle is not definitely occluded? */
+ st_theme_node_get_background_color (node, &background_color);
+ if (!clutter_color_equal (&background_color, &invisible_occluded) ||
+ paint_opacity < 255 ||
+ xoffset > shadow_blur_radius || left < 0 ||
+ yoffset > shadow_blur_radius || top < 0)
+ {
+ rectangles[idx++] = left;
+ rectangles[idx++] = top;
+ rectangles[idx++] = right;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_bottom;
+ }
+
+ if (xend > right)
+ {
+ /* Right middle */
+ rectangles[idx++] = right;
+ rectangles[idx++] = top;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = s_bottom;
+ }
+
+ if (yend > bottom)
+ {
+ if (xoffset < left)
+ {
+ /* Bottom left corner */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = left;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = 1;
+ }
+
+ /* Bottom middle */
+ rectangles[idx++] = left;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = right;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = 1;
+
+ if (xend > right)
+ {
+ /* Bottom right corner */
+ rectangles[idx++] = right;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = 1;
+ }
+ }
+
+ cogl_framebuffer_draw_textured_rectangles (framebuffer, state->box_shadow_pipeline,
+ rectangles, idx / 8);
+
+#if 0
+ /* Visual feedback on shadow's 9-slice and original offscreen buffer,
+ for debug purposes */
+ cogl_framebuffer_draw_rectangle (framebuffer, state->box_shadow_pipeline,
+ xend, yoffset, xend + shadow_width, yoffset + shadow_height);
+
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline, 0xff, 0x0, 0x0, 0xff);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xoffset, top, xend, top + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xoffset, bottom, xend, bottom + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ left, yoffset, left + 1, yend);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ right, yoffset, right + 1, yend);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset, xend + shadow_width, yoffset + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + shadow_height, xend + shadow_width, yoffset + shadow_height + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset, xend + 1, yoffset + shadow_height);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + shadow_width, yoffset, xend + shadow_width + 1, yoffset + shadow_height);
+
+ s_top *= shadow_height;
+ s_bottom *= shadow_height;
+ s_left *= shadow_width;
+ s_right *= shadow_width;
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + s_top, xend + shadow_width, yoffset + s_top + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + s_bottom, xend + shadow_width, yoffset + s_bottom + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + s_left, yoffset, xend + s_left + 1, yoffset + shadow_height);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + s_right, yoffset, xend + s_right + 1, yoffset + shadow_height);
+
+#endif
+}
+
+static void
+st_theme_node_prerender_shadow (StThemeNodePaintState *state)
+{
+ StThemeNode *node = state->node;
+ CoglContext *ctx;
+ int fb_width, fb_height;
+ CoglTexture *buffer;
+ CoglOffscreen *offscreen = NULL;
+ CoglFramebuffer *framebuffer;
+ GError *error = NULL;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ /* Render offscreen */
+ fb_width = ceilf (state->box_shadow_width * state->resource_scale);
+ fb_height = ceilf (state->box_shadow_height * state->resource_scale);
+ buffer = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, fb_width, fb_height));
+ if (buffer == NULL)
+ return;
+
+ offscreen = cogl_offscreen_new_with_texture (buffer);
+ framebuffer = COGL_FRAMEBUFFER (offscreen);
+
+ if (cogl_framebuffer_allocate (framebuffer, &error))
+ {
+ ClutterActorBox box = { 0, 0, state->box_shadow_width, state->box_shadow_height};
+
+ cogl_framebuffer_orthographic (framebuffer, 0, 0,
+ fb_width, fb_height, 0, 1.0);
+ cogl_framebuffer_scale (framebuffer,
+ state->resource_scale,
+ state->resource_scale, 1);
+ cogl_framebuffer_clear4f (framebuffer, COGL_BUFFER_BIT_COLOR, 0, 0, 0, 0);
+
+ st_theme_node_paint_borders (state, framebuffer, &box, ST_PAINT_BORDERS_MODE_SILHOUETTE, 0xFF);
+
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (st_theme_node_get_box_shadow (node),
+ buffer, state->resource_scale);
+ }
+
+ g_clear_error (&error);
+ g_clear_object (&offscreen);
+ cogl_clear_object (&buffer);
+}
+
+static void
+st_theme_node_compute_maximum_borders (StThemeNodePaintState *state)
+{
+ int max_borders[4], center_radius;
+ StThemeNode * node = state->node;
+
+ /* Compute maximum borders sizes */
+ max_borders[ST_SIDE_TOP] = MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_TOPRIGHT]);
+ max_borders[ST_SIDE_BOTTOM] = MAX (node->border_radius[ST_CORNER_BOTTOMLEFT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+ max_borders[ST_SIDE_LEFT] = MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_BOTTOMLEFT]);
+ max_borders[ST_SIDE_RIGHT] = MAX (node->border_radius[ST_CORNER_TOPRIGHT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+
+ center_radius = (node->box_shadow->blur > 0) ? (2 * node->box_shadow->blur + 1) : 1;
+
+ node->box_shadow_min_width = max_borders[ST_SIDE_LEFT] + max_borders[ST_SIDE_RIGHT] + center_radius;
+ node->box_shadow_min_height = max_borders[ST_SIDE_TOP] + max_borders[ST_SIDE_BOTTOM] + center_radius;
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ {
+ state->box_shadow_width = state->alloc_width;
+ state->box_shadow_height = state->alloc_height;
+ }
+ else
+ {
+ state->box_shadow_width = node->box_shadow_min_width;
+ state->box_shadow_height = node->box_shadow_min_height;
+ }
+}
+
+static void
+st_theme_node_paint_sliced_border_image (StThemeNode *node,
+ CoglFramebuffer *framebuffer,
+ float width,
+ float height,
+ guint8 paint_opacity)
+{
+ gfloat ex, ey;
+ gfloat tx1, ty1, tx2, ty2;
+ gint border_left, border_right, border_top, border_bottom;
+ float img_width, img_height;
+ StBorderImage *border_image;
+ CoglPipeline *pipeline;
+
+ border_image = st_theme_node_get_border_image (node);
+ g_assert (border_image != NULL);
+
+ st_border_image_get_borders (border_image,
+ &border_left, &border_right, &border_top, &border_bottom);
+
+ img_width = cogl_texture_get_width (node->border_slices_texture);
+ img_height = cogl_texture_get_height (node->border_slices_texture);
+
+ tx1 = border_left / img_width;
+ tx2 = (img_width - border_right) / img_width;
+ ty1 = border_top / img_height;
+ ty2 = (img_height - border_bottom) / img_height;
+
+ ex = width - border_right;
+ if (ex < 0)
+ ex = border_right; /* FIXME ? */
+
+ ey = height - border_bottom;
+ if (ey < 0)
+ ey = border_bottom; /* FIXME ? */
+
+ pipeline = node->border_slices_pipeline;
+ cogl_pipeline_set_color4ub (pipeline,
+ paint_opacity, paint_opacity, paint_opacity, paint_opacity);
+
+ {
+ float rectangles[] =
+ {
+ /* top left corner */
+ 0, 0, border_left, border_top,
+ 0.0, 0.0,
+ tx1, ty1,
+
+ /* top middle */
+ border_left, 0, ex, border_top,
+ tx1, 0.0,
+ tx2, ty1,
+
+ /* top right */
+ ex, 0, width, border_top,
+ tx2, 0.0,
+ 1.0, ty1,
+
+ /* mid left */
+ 0, border_top, border_left, ey,
+ 0.0, ty1,
+ tx1, ty2,
+
+ /* center */
+ border_left, border_top, ex, ey,
+ tx1, ty1,
+ tx2, ty2,
+
+ /* mid right */
+ ex, border_top, width, ey,
+ tx2, ty1,
+ 1.0, ty2,
+
+ /* bottom left */
+ 0, ey, border_left, height,
+ 0.0, ty2,
+ tx1, 1.0,
+
+ /* bottom center */
+ border_left, ey, ex, height,
+ tx1, ty2,
+ tx2, 1.0,
+
+ /* bottom right */
+ ex, ey, width, height,
+ tx2, ty2,
+ 1.0, 1.0
+ };
+
+ cogl_framebuffer_draw_textured_rectangles (framebuffer, pipeline, rectangles, 9);
+ }
+}
+
+static void
+st_theme_node_paint_outline (StThemeNode *node,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity)
+
+{
+ float width, height;
+ int outline_width;
+ float rects[16];
+ ClutterColor outline_color, effective_outline;
+ guint8 alpha;
+
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+
+ outline_width = st_theme_node_get_outline_width (node);
+ if (outline_width == 0)
+ return;
+
+ st_theme_node_get_outline_color (node, &outline_color);
+ over (&outline_color, &node->background_color, &effective_outline);
+
+ alpha = paint_opacity * outline_color.alpha / 255;
+
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ effective_outline.red * alpha / 255,
+ effective_outline.green * alpha / 255,
+ effective_outline.blue * alpha / 255,
+ alpha);
+
+ /* The outline is drawn just outside the border, which means just
+ * outside the allocation box. This means that in some situations
+ * involving clip_to_allocation or the screen edges, you won't be
+ * able to see the outline. In practice, it works well enough.
+ */
+
+ /* NORTH */
+ rects[0] = -outline_width;
+ rects[1] = -outline_width;
+ rects[2] = width + outline_width;
+ rects[3] = 0;
+
+ /* EAST */
+ rects[4] = width;
+ rects[5] = 0;
+ rects[6] = width + outline_width;
+ rects[7] = height;
+
+ /* SOUTH */
+ rects[8] = -outline_width;
+ rects[9] = height;
+ rects[10] = width + outline_width;
+ rects[11] = height + outline_width;
+
+ /* WEST */
+ rects[12] = -outline_width;
+ rects[13] = 0;
+ rects[14] = 0;
+ rects[15] = height;
+
+ cogl_framebuffer_draw_rectangles (framebuffer, node->color_pipeline, rects, 4);
+}
+
+static gboolean
+st_theme_node_needs_new_box_shadow_for_size (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ if (!node->rendered_once)
+ return TRUE;
+
+ /* The resource scale changed, so need to recompute a new box-shadow */
+ if (fabsf (state->resource_scale - resource_scale) > FLT_EPSILON)
+ return TRUE;
+
+ /* The allocation hasn't changed, no need to recompute a new
+ box-shadow. */
+ if (state->alloc_width == width &&
+ state->alloc_height == height)
+ return FALSE;
+
+ /* If there is no shadow, no need to recompute a new box-shadow. */
+ if (node->box_shadow_min_width == 0 ||
+ node->box_shadow_min_height == 0)
+ return FALSE;
+
+ /* If the new size is inferior to the box-shadow minimum size (we
+ already know the size has changed), we need to recompute the
+ box-shadow. */
+ if (width < node->box_shadow_min_width ||
+ height < node->box_shadow_min_height)
+ return TRUE;
+
+ /* Now checking whether the size of the node has crossed the minimum
+ box-shadow size boundary, from below to above the minimum size .
+ If that's the case, we need to recompute the box-shadow */
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+st_theme_node_paint (StThemeNode *node,
+ StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity,
+ float resource_scale)
+{
+ float width, height;
+ ClutterActorBox allocation;
+
+ /* Some things take an ActorBox, some things just width/height */
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+ allocation.x1 = allocation.y1 = 0;
+ allocation.x2 = width;
+ allocation.y2 = height;
+
+ if (width <= 0 || height <= 0 || resource_scale <= 0.0f)
+ return;
+
+ /* Check whether we need to recreate the textures of the paint
+ * state, either because :
+ * 1) the theme node associated to the paint state has changed
+ * 2) the allocation size change requires recreating textures
+ */
+ if (state->node != node ||
+ st_theme_node_needs_new_box_shadow_for_size (state, node, width, height,
+ resource_scale))
+ {
+ /* If we had the ability to cache textures on the node, then we
+ can just copy them over to the paint state and avoid all
+ rendering. We end up sharing textures a cross different
+ widgets. */
+ if (node->rendered_once && node->cached_textures &&
+ width >= node->box_shadow_min_width && height >= node->box_shadow_min_height &&
+ fabsf (resource_scale - state->resource_scale) < FLT_EPSILON)
+ st_theme_node_paint_state_copy (state, &node->cached_state);
+ else
+ st_theme_node_render_resources (state, node, width, height, resource_scale);
+
+ node->rendered_once = TRUE;
+ }
+ else if (state->alloc_width != width || state->alloc_height != height ||
+ fabsf (state->resource_scale - resource_scale) > FLT_EPSILON)
+ st_theme_node_update_resources (state, node, width, height, resource_scale);
+
+ /* Rough notes about the relationship of borders and backgrounds in CSS3;
+ * see http://www.w3.org/TR/css3-background/ for more accurate details.
+ *
+ * - Things are drawn in 4 layers, from the bottom:
+ * Background color
+ * Background image
+ * Border color or border image
+ * Content
+ * - The background color, gradient and image extend to and are clipped by
+ * the edge of the border area, so will be rounded if the border is
+ * rounded. (CSS3 background-clip property modifies this)
+ * - The border image replaces what would normally be drawn by the border
+ * - The border image is not clipped by a rounded border-radius
+ * - The border radius rounds the background even if the border is
+ * zero width or a border image is being used.
+ *
+ * Deviations from the above as implemented here:
+ * - The combination of border image and a non-zero border radius is
+ * not supported; the background color will be drawn with square
+ * corners.
+ * - The background image is drawn above the border color, not below it.
+ * - We clip the background image to the inside edges of the border
+ * instead of the outside edges of the border (but position the image
+ * such that it's aligned to the outside edges)
+ */
+
+ if (state->box_shadow_pipeline)
+ {
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ _st_paint_shadow_with_opacity (node->box_shadow,
+ framebuffer,
+ state->box_shadow_pipeline,
+ &allocation,
+ paint_opacity);
+ else
+ st_theme_node_paint_sliced_shadow (state,
+ framebuffer,
+ &allocation,
+ paint_opacity);
+ }
+
+ if (state->prerendered_pipeline != NULL ||
+ st_theme_node_load_border_image (node, resource_scale))
+ {
+ if (state->prerendered_pipeline != NULL)
+ {
+ ClutterActorBox paint_box;
+
+ st_theme_node_get_background_paint_box (node,
+ &allocation,
+ &paint_box);
+
+ paint_material_with_opacity (state->prerendered_pipeline,
+ framebuffer,
+ &paint_box,
+ NULL,
+ paint_opacity);
+ }
+
+ if (node->border_slices_pipeline != NULL)
+ st_theme_node_paint_sliced_border_image (node, framebuffer, width, height, paint_opacity);
+ }
+ else
+ {
+ st_theme_node_paint_borders (state, framebuffer, box, ST_PAINT_BORDERS_MODE_COLOR, paint_opacity);
+ }
+
+ st_theme_node_paint_outline (node, framebuffer, box, paint_opacity);
+
+ if (state->prerendered_pipeline == NULL &&
+ st_theme_node_load_background_image (node, resource_scale))
+ {
+ ClutterActorBox background_box;
+ ClutterActorBox texture_coords;
+ gboolean has_visible_outline;
+
+ /* If the node doesn't have an opaque or repeating background or
+ * a border then we let its background image shadows leak out,
+ * but otherwise we clip it.
+ */
+ has_visible_outline = st_theme_node_has_visible_outline (node);
+
+ get_background_position (node, &allocation, resource_scale,
+ &background_box, &texture_coords);
+
+ if (has_visible_outline || node->background_repeat)
+ cogl_framebuffer_push_rectangle_clip (framebuffer,
+ allocation.x1, allocation.y1,
+ allocation.x2, allocation.y2);
+
+ /* CSS based drop shadows
+ *
+ * Drop shadows in ST are modelled after the CSS3 box-shadow property;
+ * see http://www.css3.info/preview/box-shadow/ for a detailed description.
+ *
+ * While the syntax of the property is mostly identical - we do not support
+ * multiple shadows and allow for a more liberal placement of the color
+ * parameter - its interpretation defers significantly in that the shadow's
+ * shape is not determined by the bounding box, but by the CSS background
+ * image. The drop shadows are allowed to escape the nodes allocation if
+ * there is nothing (like a border, or the edge of the background color)
+ * to logically confine it.
+ */
+ if (node->background_shadow_pipeline != NULL)
+ _st_paint_shadow_with_opacity (node->background_image_shadow,
+ framebuffer,
+ node->background_shadow_pipeline,
+ &background_box,
+ paint_opacity);
+
+ paint_material_with_opacity (node->background_pipeline,
+ framebuffer,
+ &background_box,
+ &texture_coords,
+ paint_opacity);
+
+ if (has_visible_outline || node->background_repeat)
+ cogl_framebuffer_pop_clip (framebuffer);
+ }
+}
+
+static void
+st_theme_node_paint_state_node_free_internal (StThemeNodePaintState *state,
+ gboolean unref_node)
+{
+ int corner_id;
+
+ cogl_clear_object (&state->prerendered_texture);
+ cogl_clear_object (&state->prerendered_pipeline);
+ cogl_clear_object (&state->box_shadow_pipeline);
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ cogl_clear_object (&state->corner_material[corner_id]);
+
+ if (unref_node)
+ st_theme_node_paint_state_set_node (state, NULL);
+
+ st_theme_node_paint_state_init (state);
+}
+
+static void
+st_theme_node_paint_state_node_freed (StThemeNodePaintState *state)
+{
+ st_theme_node_paint_state_node_free_internal (state, FALSE);
+}
+
+void
+st_theme_node_paint_state_set_node (StThemeNodePaintState *state, StThemeNode *node)
+{
+ if (state->node)
+ g_object_weak_unref (G_OBJECT (state->node),
+ (GWeakNotify) st_theme_node_paint_state_node_freed,
+ state);
+
+ state->node = node;
+ if (state->node)
+ g_object_weak_ref (G_OBJECT (state->node),
+ (GWeakNotify) st_theme_node_paint_state_node_freed,
+ state);
+}
+
+void
+st_theme_node_paint_state_free (StThemeNodePaintState *state)
+{
+ st_theme_node_paint_state_node_free_internal (state, TRUE);
+}
+
+void
+st_theme_node_paint_state_init (StThemeNodePaintState *state)
+{
+ int corner_id;
+
+ state->alloc_width = 0;
+ state->alloc_height = 0;
+ state->resource_scale = -1;
+ state->node = NULL;
+ state->box_shadow_pipeline = NULL;
+ state->prerendered_texture = NULL;
+ state->prerendered_pipeline = NULL;
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ state->corner_material[corner_id] = NULL;
+}
+
+void
+st_theme_node_paint_state_copy (StThemeNodePaintState *state,
+ StThemeNodePaintState *other)
+{
+ int corner_id;
+
+ if (state == other)
+ return;
+
+ st_theme_node_paint_state_free (state);
+
+ st_theme_node_paint_state_set_node (state, other->node);
+
+ state->alloc_width = other->alloc_width;
+ state->alloc_height = other->alloc_height;
+ state->resource_scale = other->resource_scale;
+ state->box_shadow_width = other->box_shadow_width;
+ state->box_shadow_height = other->box_shadow_height;
+
+ if (other->box_shadow_pipeline)
+ state->box_shadow_pipeline = cogl_object_ref (other->box_shadow_pipeline);
+ if (other->prerendered_texture)
+ state->prerendered_texture = cogl_object_ref (other->prerendered_texture);
+ if (other->prerendered_pipeline)
+ state->prerendered_pipeline = cogl_object_ref (other->prerendered_pipeline);
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ if (other->corner_material[corner_id])
+ state->corner_material[corner_id] = cogl_object_ref (other->corner_material[corner_id]);
+}
+
+void
+st_theme_node_paint_state_invalidate (StThemeNodePaintState *state)
+{
+ state->alloc_width = 0;
+ state->alloc_height = 0;
+ state->resource_scale = -1.0f;
+}
+
+gboolean
+st_theme_node_paint_state_invalidate_for_file (StThemeNodePaintState *state,
+ GFile *file)
+{
+ if (state->node != NULL &&
+ st_theme_node_invalidate_resources_for_file (state->node, file))
+ {
+ st_theme_node_paint_state_invalidate (state);
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/src/st/st-theme-node-private.h b/src/st/st-theme-node-private.h
new file mode 100644
index 0000000..7533482
--- /dev/null
+++ b/src/st/st-theme-node-private.h
@@ -0,0 +1,131 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-private.h: private structures and functions for StThemeNode
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2011 Quentin "Sardem FF7" Glidic
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_NODE_PRIVATE_H__
+#define __ST_THEME_NODE_PRIVATE_H__
+
+#include <gdk/gdk.h>
+
+#include "st-theme-node.h"
+#include "croco/libcroco.h"
+#include "st-types.h"
+
+G_BEGIN_DECLS
+
+struct _StThemeNode {
+ GObject parent;
+
+ StThemeContext *context;
+ StThemeNode *parent_node;
+ StTheme *theme;
+
+ PangoFontDescription *font_desc;
+
+ ClutterColor background_color;
+ /* If gradient is set, then background_color is the gradient start */
+ StGradientType background_gradient_type;
+ ClutterColor background_gradient_end;
+
+ int background_position_x;
+ int background_position_y;
+
+ StBackgroundSize background_size;
+ gint background_size_w;
+ gint background_size_h;
+
+ ClutterColor foreground_color;
+ ClutterColor border_color[4];
+ ClutterColor outline_color;
+
+ int border_width[4];
+ int border_radius[4];
+ int outline_width;
+ guint padding[4];
+ guint margin[4];
+
+ int width;
+ int height;
+ int min_width;
+ int min_height;
+ int max_width;
+ int max_height;
+
+ int transition_duration;
+
+ GFile *background_image;
+ StBorderImage *border_image;
+ StShadow *box_shadow;
+ StShadow *background_image_shadow;
+ StShadow *text_shadow;
+ StIconColors *icon_colors;
+
+ GType element_type;
+ char *element_id;
+ GStrv element_classes;
+ GStrv pseudo_classes;
+ char *inline_style;
+
+ CRDeclaration **properties;
+ int n_properties;
+
+ /* We hold onto these separately so we can destroy them on finalize */
+ CRDeclaration *inline_properties;
+
+ guint background_position_set : 1;
+ guint background_repeat : 1;
+
+ guint properties_computed : 1;
+ guint geometry_computed : 1;
+ guint background_computed : 1;
+ guint foreground_computed : 1;
+ guint border_image_computed : 1;
+ guint box_shadow_computed : 1;
+ guint background_image_shadow_computed : 1;
+ guint text_shadow_computed : 1;
+ guint link_type : 2;
+ guint rendered_once : 1;
+ guint cached_textures : 1;
+
+ int box_shadow_min_width;
+ int box_shadow_min_height;
+
+ guint stylesheets_changed_id;
+
+ CoglPipeline *border_slices_texture;
+ CoglPipeline *border_slices_pipeline;
+ CoglPipeline *background_texture;
+ CoglPipeline *background_pipeline;
+ CoglPipeline *background_shadow_pipeline;
+ CoglPipeline *color_pipeline;
+
+ StThemeNodePaintState cached_state;
+
+ int cached_scale_factor;
+};
+
+void _st_theme_node_ensure_background (StThemeNode *node);
+void _st_theme_node_ensure_geometry (StThemeNode *node);
+void _st_theme_node_apply_margins (StThemeNode *node,
+ ClutterActor *actor);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_NODE_PRIVATE_H__ */
diff --git a/src/st/st-theme-node-transition.c b/src/st/st-theme-node-transition.c
new file mode 100644
index 0000000..20b1476
--- /dev/null
+++ b/src/st/st-theme-node-transition.c
@@ -0,0 +1,470 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-transition.c: Theme node transitions for StWidget.
+ *
+ * Copyright 2010 Florian Müllner
+ * Copyright 2010 Adel Gadllah
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+
+#include "st-theme-node-transition.h"
+
+enum {
+ COMPLETED,
+ NEW_FRAME,
+ LAST_SIGNAL
+};
+
+typedef struct _StThemeNodeTransitionPrivate StThemeNodeTransitionPrivate;
+
+struct _StThemeNodeTransition {
+ GObject parent;
+
+ StThemeNodeTransitionPrivate *priv;
+};
+
+struct _StThemeNodeTransitionPrivate {
+ StThemeNode *old_theme_node;
+ StThemeNode *new_theme_node;
+
+ StThemeNodePaintState old_paint_state;
+ StThemeNodePaintState new_paint_state;
+
+ CoglTexture *old_texture;
+ CoglTexture *new_texture;
+
+ CoglFramebuffer *old_offscreen;
+ CoglFramebuffer *new_offscreen;
+
+ CoglPipeline *material;
+
+ ClutterTimeline *timeline;
+
+ gulong timeline_completed_id;
+ gulong timeline_new_frame_id;
+
+ ClutterActorBox last_allocation;
+ ClutterActorBox offscreen_box;
+
+ gboolean needs_setup;
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StThemeNodeTransition, st_theme_node_transition, G_TYPE_OBJECT);
+
+
+static void
+on_timeline_completed (ClutterTimeline *timeline,
+ StThemeNodeTransition *transition)
+{
+ g_signal_emit (transition, signals[COMPLETED], 0);
+}
+
+static void
+on_timeline_new_frame (ClutterTimeline *timeline,
+ gint frame_num,
+ StThemeNodeTransition *transition)
+{
+ g_signal_emit (transition, signals[NEW_FRAME], 0);
+}
+
+StThemeNodeTransition *
+st_theme_node_transition_new (ClutterActor *actor,
+ StThemeNode *from_node,
+ StThemeNode *to_node,
+ StThemeNodePaintState *old_paint_state,
+ unsigned int duration)
+{
+ StThemeNodeTransition *transition;
+ g_return_val_if_fail (ST_IS_THEME_NODE (from_node), NULL);
+ g_return_val_if_fail (ST_IS_THEME_NODE (to_node), NULL);
+
+ duration = st_theme_node_get_transition_duration (to_node);
+
+ transition = g_object_new (ST_TYPE_THEME_NODE_TRANSITION, NULL);
+
+ transition->priv->old_theme_node = g_object_ref (from_node);
+ transition->priv->new_theme_node = g_object_ref (to_node);
+
+ st_theme_node_paint_state_copy (&transition->priv->old_paint_state,
+ old_paint_state);
+
+ transition->priv->timeline = clutter_timeline_new_for_actor (actor, duration);
+
+ transition->priv->timeline_completed_id =
+ g_signal_connect (transition->priv->timeline, "completed",
+ G_CALLBACK (on_timeline_completed), transition);
+ transition->priv->timeline_new_frame_id =
+ g_signal_connect (transition->priv->timeline, "new-frame",
+ G_CALLBACK (on_timeline_new_frame), transition);
+
+ clutter_timeline_set_progress_mode (transition->priv->timeline, CLUTTER_EASE_IN_OUT_QUAD);
+
+ clutter_timeline_start (transition->priv->timeline);
+
+ return transition;
+}
+
+/**
+ * st_theme_node_transition_get_new_paint_state: (skip)
+ *
+ */
+StThemeNodePaintState *
+st_theme_node_transition_get_new_paint_state (StThemeNodeTransition *transition)
+{
+ return &transition->priv->new_paint_state;
+}
+
+void
+st_theme_node_transition_update (StThemeNodeTransition *transition,
+ StThemeNode *new_node)
+{
+ StThemeNodeTransitionPrivate *priv;
+ StThemeNode *old_node;
+ ClutterTimelineDirection direction;
+
+ g_return_if_fail (ST_IS_THEME_NODE_TRANSITION (transition));
+ g_return_if_fail (ST_IS_THEME_NODE (new_node));
+
+ priv = transition->priv;
+ direction = clutter_timeline_get_direction (priv->timeline);
+ old_node = (direction == CLUTTER_TIMELINE_FORWARD) ? priv->old_theme_node
+ : priv->new_theme_node;
+
+ /* If the update is the reversal of the current transition,
+ * we reverse the timeline.
+ * Otherwise, we should initiate a new transition from the
+ * current state to the new one; this is hard to do if the
+ * transition is in an intermediate state, so we just cancel
+ * the ongoing transition in that case.
+ * Note that reversing a timeline before any time elapsed
+ * results in the timeline's time position being set to the
+ * full duration - this is not what we want, so we cancel the
+ * transition as well in that case.
+ */
+ if (st_theme_node_equal (new_node, old_node))
+ {
+ {
+ StThemeNodePaintState tmp;
+
+ st_theme_node_paint_state_init (&tmp);
+ st_theme_node_paint_state_copy (&tmp, &priv->old_paint_state);
+ st_theme_node_paint_state_copy (&priv->old_paint_state, &priv->new_paint_state);
+ st_theme_node_paint_state_copy (&priv->new_paint_state, &tmp);
+ st_theme_node_paint_state_free (&tmp);
+ }
+
+ if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
+ {
+ if (direction == CLUTTER_TIMELINE_FORWARD)
+ clutter_timeline_set_direction (priv->timeline,
+ CLUTTER_TIMELINE_BACKWARD);
+ else
+ clutter_timeline_set_direction (priv->timeline,
+ CLUTTER_TIMELINE_FORWARD);
+ }
+ else
+ {
+ clutter_timeline_stop (priv->timeline);
+ g_signal_emit (transition, signals[COMPLETED], 0);
+ }
+ }
+ else
+ {
+ if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
+ {
+ clutter_timeline_stop (priv->timeline);
+ g_signal_emit (transition, signals[COMPLETED], 0);
+ }
+ else
+ {
+ guint new_duration = st_theme_node_get_transition_duration (new_node);
+
+ clutter_timeline_set_duration (priv->timeline, new_duration);
+
+ g_object_unref (priv->new_theme_node);
+ priv->new_theme_node = g_object_ref (new_node);
+
+ st_theme_node_paint_state_invalidate (&priv->new_paint_state);
+ }
+ }
+}
+
+static void
+calculate_offscreen_box (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation)
+{
+ ClutterActorBox paint_box;
+
+ st_theme_node_transition_get_paint_box (transition,
+ allocation,
+ &paint_box);
+ transition->priv->offscreen_box.x1 = paint_box.x1 - allocation->x1;
+ transition->priv->offscreen_box.y1 = paint_box.y1 - allocation->y1;
+ transition->priv->offscreen_box.x2 = paint_box.x2 - allocation->x1;
+ transition->priv->offscreen_box.y2 = paint_box.y2 - allocation->y1;
+}
+
+void
+st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box)
+{
+ StThemeNodeTransitionPrivate *priv = transition->priv;
+ ClutterActorBox old_node_box, new_node_box;
+
+ st_theme_node_get_paint_box (priv->old_theme_node,
+ allocation,
+ &old_node_box);
+
+ st_theme_node_get_paint_box (priv->new_theme_node,
+ allocation,
+ &new_node_box);
+
+ paint_box->x1 = MIN (old_node_box.x1, new_node_box.x1);
+ paint_box->y1 = MIN (old_node_box.y1, new_node_box.y1);
+ paint_box->x2 = MAX (old_node_box.x2, new_node_box.x2);
+ paint_box->y2 = MAX (old_node_box.y2, new_node_box.y2);
+}
+
+static gboolean
+setup_framebuffers (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation,
+ float resource_scale)
+{
+ StThemeNodeTransitionPrivate *priv = transition->priv;
+ CoglContext *ctx;
+ guint width, height;
+ GError *catch_error = NULL;
+
+ /* template material to avoid unnecessary shader compilation */
+ static CoglPipeline *material_template = NULL;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ width = ceilf ((priv->offscreen_box.x2 - priv->offscreen_box.x1) * resource_scale);
+ height = ceilf ((priv->offscreen_box.y2 - priv->offscreen_box.y1) * resource_scale);
+
+ g_return_val_if_fail (width > 0, FALSE);
+ g_return_val_if_fail (height > 0, FALSE);
+
+ cogl_clear_object (&priv->old_texture);
+ priv->old_texture = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, width, height));
+
+ cogl_clear_object (&priv->new_texture);
+ priv->new_texture = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, width, height));
+
+ if (priv->old_texture == NULL)
+ return FALSE;
+
+ if (priv->new_texture == NULL)
+ return FALSE;
+
+ g_clear_object (&priv->old_offscreen);
+ priv->old_offscreen = COGL_FRAMEBUFFER (cogl_offscreen_new_with_texture (priv->old_texture));
+ if (!cogl_framebuffer_allocate (priv->old_offscreen, &catch_error))
+ {
+ g_error_free (catch_error);
+ g_clear_object (&priv->old_offscreen);
+ return FALSE;
+ }
+
+ g_clear_object (&priv->new_offscreen);
+ priv->new_offscreen = COGL_FRAMEBUFFER (cogl_offscreen_new_with_texture (priv->new_texture));
+ if (!cogl_framebuffer_allocate (priv->new_offscreen, &catch_error))
+ {
+ g_error_free (catch_error);
+ g_clear_object (&priv->new_offscreen);
+ return FALSE;
+ }
+
+ if (priv->material == NULL)
+ {
+ if (G_UNLIKELY (material_template == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ material_template = cogl_pipeline_new (ctx);
+
+ cogl_pipeline_set_layer_combine (material_template, 0,
+ "RGBA = REPLACE (TEXTURE)",
+ NULL);
+ cogl_pipeline_set_layer_combine (material_template, 1,
+ "RGBA = INTERPOLATE (PREVIOUS, "
+ "TEXTURE, "
+ "CONSTANT[A])",
+ NULL);
+ cogl_pipeline_set_layer_combine (material_template, 2,
+ "RGBA = MODULATE (PREVIOUS, "
+ "PRIMARY)",
+ NULL);
+ }
+ priv->material = cogl_pipeline_copy (material_template);
+ }
+
+ cogl_pipeline_set_layer_texture (priv->material, 0, priv->new_texture);
+ cogl_pipeline_set_layer_texture (priv->material, 1, priv->old_texture);
+
+ cogl_framebuffer_clear4f (priv->old_offscreen, COGL_BUFFER_BIT_COLOR,
+ 0, 0, 0, 0);
+ cogl_framebuffer_orthographic (priv->old_offscreen,
+ priv->offscreen_box.x1,
+ priv->offscreen_box.y1,
+ priv->offscreen_box.x2,
+ priv->offscreen_box.y2, 0.0, 1.0);
+
+ st_theme_node_paint (priv->old_theme_node, &priv->old_paint_state,
+ priv->old_offscreen, allocation, 255, resource_scale);
+
+ cogl_framebuffer_clear4f (priv->new_offscreen, COGL_BUFFER_BIT_COLOR,
+ 0, 0, 0, 0);
+ cogl_framebuffer_orthographic (priv->new_offscreen,
+ priv->offscreen_box.x1,
+ priv->offscreen_box.y1,
+ priv->offscreen_box.x2,
+ priv->offscreen_box.y2, 0.0, 1.0);
+ st_theme_node_paint (priv->new_theme_node, &priv->new_paint_state,
+ priv->new_offscreen, allocation, 255, resource_scale);
+
+ return TRUE;
+}
+
+void
+st_theme_node_transition_paint (StThemeNodeTransition *transition,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *allocation,
+ guint8 paint_opacity,
+ float resource_scale)
+{
+ StThemeNodeTransitionPrivate *priv = transition->priv;
+
+ CoglColor constant;
+ float tex_coords[] = {
+ 0.0, 0.0, 1.0, 1.0,
+ 0.0, 0.0, 1.0, 1.0,
+ };
+
+ g_return_if_fail (ST_IS_THEME_NODE (priv->old_theme_node));
+ g_return_if_fail (ST_IS_THEME_NODE (priv->new_theme_node));
+
+ if (!clutter_actor_box_equal (allocation, &priv->last_allocation))
+ priv->needs_setup = TRUE;
+
+ if (priv->needs_setup)
+ {
+ priv->last_allocation = *allocation;
+
+ calculate_offscreen_box (transition, allocation);
+ priv->needs_setup = clutter_actor_box_get_area (&priv->offscreen_box) == 0 ||
+ !setup_framebuffers (transition, allocation,
+ resource_scale);
+
+ if (priv->needs_setup) /* setting up framebuffers failed */
+ return;
+ }
+
+ cogl_color_init_from_4f (&constant, 0., 0., 0.,
+ clutter_timeline_get_progress (priv->timeline));
+ cogl_pipeline_set_layer_combine_constant (priv->material, 1, &constant);
+
+ cogl_pipeline_set_color4ub (priv->material,
+ paint_opacity, paint_opacity,
+ paint_opacity, paint_opacity);
+
+ cogl_framebuffer_draw_multitextured_rectangle (framebuffer,
+ priv->material,
+ priv->offscreen_box.x1,
+ priv->offscreen_box.y1,
+ priv->offscreen_box.x2,
+ priv->offscreen_box.y2,
+ tex_coords, 8);
+}
+
+static void
+st_theme_node_transition_dispose (GObject *object)
+{
+ StThemeNodeTransitionPrivate *priv = ST_THEME_NODE_TRANSITION (object)->priv;
+
+ g_clear_object (&priv->old_theme_node);
+ g_clear_object (&priv->new_theme_node);
+
+ cogl_clear_object (&priv->old_texture);
+ cogl_clear_object (&priv->new_texture);
+
+ g_clear_object (&priv->old_offscreen);
+ g_clear_object (&priv->new_offscreen);
+
+ cogl_clear_object (&priv->material);
+
+ if (priv->timeline)
+ {
+ g_clear_signal_handler (&priv->timeline_completed_id, priv->timeline);
+ g_clear_signal_handler (&priv->timeline_new_frame_id, priv->timeline);
+
+ g_clear_object (&priv->timeline);
+ }
+
+ priv->timeline_completed_id = 0;
+ priv->timeline_new_frame_id = 0;
+
+ st_theme_node_paint_state_free (&priv->old_paint_state);
+ st_theme_node_paint_state_free (&priv->new_paint_state);
+
+ G_OBJECT_CLASS (st_theme_node_transition_parent_class)->dispose (object);
+}
+
+static void
+st_theme_node_transition_init (StThemeNodeTransition *transition)
+{
+ transition->priv = st_theme_node_transition_get_instance_private (transition);
+
+ transition->priv->old_theme_node = NULL;
+ transition->priv->new_theme_node = NULL;
+
+ transition->priv->old_texture = NULL;
+ transition->priv->new_texture = NULL;
+
+ transition->priv->old_offscreen = NULL;
+ transition->priv->new_offscreen = NULL;
+
+ st_theme_node_paint_state_init (&transition->priv->old_paint_state);
+ st_theme_node_paint_state_init (&transition->priv->new_paint_state);
+
+ transition->priv->needs_setup = TRUE;
+}
+
+static void
+st_theme_node_transition_class_init (StThemeNodeTransitionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = st_theme_node_transition_dispose;
+
+ signals[COMPLETED] =
+ g_signal_new ("completed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[NEW_FRAME] =
+ g_signal_new ("new-frame",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
diff --git a/src/st/st-theme-node-transition.h b/src/st/st-theme-node-transition.h
new file mode 100644
index 0000000..e7420e6
--- /dev/null
+++ b/src/st/st-theme-node-transition.h
@@ -0,0 +1,58 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-transition.h: Theme node transitions for StWidget.
+ *
+ * Copyright 2010 Florian Müllner
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_NODE_TRANSITION_H__
+#define __ST_THEME_NODE_TRANSITION_H__
+
+#include <clutter/clutter.h>
+
+#include "st-widget.h"
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_THEME_NODE_TRANSITION (st_theme_node_transition_get_type ())
+G_DECLARE_FINAL_TYPE (StThemeNodeTransition, st_theme_node_transition,
+ ST, THEME_NODE_TRANSITION, GObject)
+
+StThemeNodeTransition *st_theme_node_transition_new (ClutterActor *actor,
+ StThemeNode *from_node,
+ StThemeNode *to_node,
+ StThemeNodePaintState *old_paint_state,
+ guint duration);
+
+void st_theme_node_transition_update (StThemeNodeTransition *transition,
+ StThemeNode *new_node);
+
+void st_theme_node_transition_paint (StThemeNodeTransition *transition,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *allocation,
+ guint8 paint_opacity,
+ float resource_scale);
+
+void st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box);
+
+StThemeNodePaintState * st_theme_node_transition_get_new_paint_state (StThemeNodeTransition *transition);
+
+G_END_DECLS
+
+#endif
diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c
new file mode 100644
index 0000000..6e09c39
--- /dev/null
+++ b/src/st/st-theme-node.c
@@ -0,0 +1,4325 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node.c: style information for one node in a tree of themed objects
+ *
+ * Copyright 2008-2010 Red Hat, Inc.
+ * Copyright 2009 Steve Frécinaux
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Adel Gadllah
+ * Copyright 2010 Giovanni Campagna
+ * Copyright 2011 Quentin "Sardem FF7" Glidic
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "st-settings.h"
+#include "st-theme-private.h"
+#include "st-theme-context.h"
+#include "st-theme-node-private.h"
+
+static void st_theme_node_dispose (GObject *object);
+static void st_theme_node_finalize (GObject *object);
+
+static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff };
+static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 };
+static const ClutterColor DEFAULT_SUCCESS_COLOR = { 0x4e, 0x9a, 0x06, 0xff };
+static const ClutterColor DEFAULT_WARNING_COLOR = { 0xf5, 0x79, 0x3e, 0xff };
+static const ClutterColor DEFAULT_ERROR_COLOR = { 0xcc, 0x00, 0x00, 0xff };
+
+G_DEFINE_TYPE (StThemeNode, st_theme_node, G_TYPE_OBJECT)
+
+static void
+st_theme_node_init (StThemeNode *node)
+{
+ node->transition_duration = -1;
+
+ st_theme_node_paint_state_init (&node->cached_state);
+}
+
+static void
+st_theme_node_class_init (StThemeNodeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = st_theme_node_dispose;
+ object_class->finalize = st_theme_node_finalize;
+}
+
+static void
+maybe_free_properties (StThemeNode *node)
+{
+ if (node->properties)
+ {
+ g_free (node->properties);
+ node->properties = NULL;
+ node->n_properties = 0;
+ }
+
+ if (node->inline_properties)
+ {
+ /* This destroys the list, not just the head of the list */
+ cr_declaration_destroy (node->inline_properties);
+ node->inline_properties = NULL;
+ }
+}
+
+static void
+st_theme_node_dispose (GObject *gobject)
+{
+ StThemeNode *node = ST_THEME_NODE (gobject);
+
+ if (node->parent_node)
+ {
+ g_object_unref (node->parent_node);
+ node->parent_node = NULL;
+ }
+
+ if (node->border_image)
+ {
+ g_object_unref (node->border_image);
+ node->border_image = NULL;
+ }
+
+ if (node->icon_colors)
+ {
+ st_icon_colors_unref (node->icon_colors);
+ node->icon_colors = NULL;
+ }
+
+ st_theme_node_paint_state_free (&node->cached_state);
+
+ g_clear_object (&node->theme);
+
+ G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (gobject);
+}
+
+static void
+st_theme_node_finalize (GObject *object)
+{
+ StThemeNode *node = ST_THEME_NODE (object);
+
+ g_free (node->element_id);
+ g_strfreev (node->element_classes);
+ g_strfreev (node->pseudo_classes);
+ g_free (node->inline_style);
+
+ maybe_free_properties (node);
+
+ g_clear_pointer (&node->font_desc, pango_font_description_free);
+
+ g_clear_pointer (&node->box_shadow, st_shadow_unref);
+ g_clear_pointer (&node->background_image_shadow, st_shadow_unref);
+ g_clear_pointer (&node->text_shadow, st_shadow_unref);
+
+ g_clear_object (&node->background_image);
+
+ cogl_clear_object (&node->background_texture);
+ cogl_clear_object (&node->background_pipeline);
+ cogl_clear_object (&node->background_shadow_pipeline);
+ cogl_clear_object (&node->border_slices_texture);
+ cogl_clear_object (&node->border_slices_pipeline);
+ cogl_clear_object (&node->color_pipeline);
+
+ G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object);
+}
+
+static GStrv
+split_on_whitespace (const gchar *s)
+{
+ gchar *cur;
+ gchar *l;
+ gchar *temp;
+ GPtrArray *arr;
+
+ if (s == NULL)
+ return NULL;
+
+ arr = g_ptr_array_new ();
+ l = g_strdup (s);
+
+ cur = strtok_r (l, " \t\f\r\n", &temp);
+
+ while (cur != NULL)
+ {
+ g_ptr_array_add (arr, g_strdup (cur));
+ cur = strtok_r (NULL, " \t\f\r\n", &temp);
+ }
+
+ g_free (l);
+ g_ptr_array_add (arr, NULL);
+ return (GStrv) g_ptr_array_free (arr, FALSE);
+}
+
+/**
+ * st_theme_node_new:
+ * @context: the context representing global state for this themed tree
+ * @parent_node: (nullable): the parent node of this node
+ * @theme: (nullable): a theme (stylesheet set) that overrides the
+ * theme inherited from the parent node
+ * @element_type: the type of the GObject represented by this node
+ * in the tree (corresponding to an element if we were theming an XML
+ * document. %G_TYPE_NONE means this style was created for the stage
+ * actor and matches a selector element name of 'stage'.
+ * @element_id: (nullable): the ID to match CSS rules against
+ * @element_class: (nullable): a whitespace-separated list of classes
+ * to match CSS rules against
+ * @pseudo_class: (nullable): a whitespace-separated list of pseudo-classes
+ * (like 'hover' or 'visited') to match CSS rules against
+ *
+ * Creates a new #StThemeNode. Once created, a node is immutable. If any
+ * of the attributes of the node (like the @element_class) change the node
+ * and its child nodes must be destroyed and recreated.
+ *
+ * Returns: (transfer full): a new #StThemeNode
+ */
+StThemeNode *
+st_theme_node_new (StThemeContext *context,
+ StThemeNode *parent_node,
+ StTheme *theme,
+ GType element_type,
+ const char *element_id,
+ const char *element_class,
+ const char *pseudo_class,
+ const char *inline_style)
+{
+ StThemeNode *node;
+
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+ g_return_val_if_fail (parent_node == NULL || ST_IS_THEME_NODE (parent_node), NULL);
+
+ node = g_object_new (ST_TYPE_THEME_NODE, NULL);
+
+ node->context = context;
+ if (parent_node != NULL)
+ node->parent_node = g_object_ref (parent_node);
+ else
+ node->parent_node = NULL;
+
+ if (theme == NULL && parent_node != NULL)
+ theme = parent_node->theme;
+
+ g_set_object (&node->theme, theme);
+ node->element_type = element_type;
+ node->element_id = g_strdup (element_id);
+ node->element_classes = split_on_whitespace (element_class);
+ node->pseudo_classes = split_on_whitespace (pseudo_class);
+ node->inline_style = g_strdup (inline_style);
+ node->cached_scale_factor = st_theme_context_get_scale_factor (context);
+
+ return node;
+}
+
+/**
+ * st_theme_node_get_parent:
+ * @node: a #StThemeNode
+ *
+ * Gets the parent themed element node.
+ *
+ * Returns: (nullable) (transfer none): the parent #StThemeNode, or %NULL if
+ * this is the root node of the tree of theme elements.
+ */
+StThemeNode *
+st_theme_node_get_parent (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->parent_node;
+}
+
+/**
+ * st_theme_node_get_theme:
+ * @node: a #StThemeNode
+ *
+ * Gets the theme stylesheet set that styles this node
+ *
+ * Returns: (transfer none): the theme stylesheet set
+ */
+StTheme *
+st_theme_node_get_theme (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->theme;
+}
+
+/**
+ * st_theme_node_get_element_type:
+ * @node: a #StThemeNode
+ *
+ * Get the element #GType for @node.
+ *
+ * Returns: the element type
+ */
+GType
+st_theme_node_get_element_type (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), G_TYPE_NONE);
+
+ return node->element_type;
+}
+
+/**
+ * st_theme_node_get_element_id:
+ * @node: a #StThemeNode
+ *
+ * Get the unique element ID for @node.
+ *
+ * Returns: (transfer none): the element's ID
+ */
+const char *
+st_theme_node_get_element_id (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->element_id;
+}
+
+/**
+ * st_theme_node_get_element_classes:
+ * @node: a #StThemeNode
+ *
+ * Get the list of element classes for @node.
+ *
+ * Returns: (transfer none): the element's classes
+ */
+GStrv
+st_theme_node_get_element_classes (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->element_classes;
+}
+
+/**
+ * st_theme_node_get_pseudo_classes:
+ * @node: a #StThemeNode
+ *
+ * Get the list of pseudo-classes for @node (eg. `:focused`).
+ *
+ * Returns: (transfer none): the element's pseudo-classes
+ */
+GStrv
+st_theme_node_get_pseudo_classes (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->pseudo_classes;
+}
+
+/**
+ * st_theme_node_equal:
+ * @node_a: first #StThemeNode
+ * @node_b: second #StThemeNode
+ *
+ * Compare two #StThemeNodes. Two nodes which compare equal will match
+ * the same CSS rules and have the same style properties. However, two
+ * nodes that have ended up with identical style properties do not
+ * necessarily compare equal.
+ *
+ * In detail, @node_a and @node_b are considered equal if and only if:
+ *
+ * - they share the same #StTheme and #StThemeContext
+ * - they have the same parent
+ * - they have the same element type
+ * - their id, class, pseudo-class and inline-style match
+ *
+ * Returns: %TRUE if @node_a equals @node_b
+ */
+gboolean
+st_theme_node_equal (StThemeNode *node_a, StThemeNode *node_b)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node_a), FALSE);
+
+ if (node_a == node_b)
+ return TRUE;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node_b), FALSE);
+
+ if (node_a->parent_node != node_b->parent_node ||
+ node_a->context != node_b->context ||
+ node_a->theme != node_b->theme ||
+ node_a->element_type != node_b->element_type ||
+ node_a->cached_scale_factor != node_b->cached_scale_factor ||
+ g_strcmp0 (node_a->element_id, node_b->element_id) ||
+ g_strcmp0 (node_a->inline_style, node_b->inline_style))
+ return FALSE;
+
+ if ((node_a->element_classes == NULL) != (node_b->element_classes == NULL))
+ return FALSE;
+
+ if ((node_a->pseudo_classes == NULL) != (node_b->pseudo_classes == NULL))
+ return FALSE;
+
+ if (node_a->element_classes != NULL)
+ {
+ int i;
+
+ for (i = 0; ; i++)
+ {
+ if (g_strcmp0 (node_a->element_classes[i],
+ node_b->element_classes[i]))
+ return FALSE;
+
+ if (node_a->element_classes[i] == NULL)
+ break;
+ }
+ }
+
+ if (node_a->pseudo_classes != NULL)
+ {
+ int i;
+
+ for (i = 0; ; i++)
+ {
+ if (g_strcmp0 (node_a->pseudo_classes[i],
+ node_b->pseudo_classes[i]))
+ return FALSE;
+
+ if (node_a->pseudo_classes[i] == NULL)
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_hash:
+ * @node: a #StThemeNode
+ *
+ * Converts @node to a hash value.
+ *
+ * Returns: a hash value corresponding to @node
+ */
+guint
+st_theme_node_hash (StThemeNode *node)
+{
+ guint hash;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), 0);
+
+ hash = GPOINTER_TO_UINT (node->parent_node);
+
+ hash = hash * 33 + GPOINTER_TO_UINT (node->context);
+ hash = hash * 33 + GPOINTER_TO_UINT (node->theme);
+ hash = hash * 33 + ((guint) node->element_type);
+ hash = hash * 33 + ((guint) node->cached_scale_factor);
+
+ if (node->element_id != NULL)
+ hash = hash * 33 + g_str_hash (node->element_id);
+
+ if (node->inline_style != NULL)
+ hash = hash * 33 + g_str_hash (node->inline_style);
+
+ if (node->element_classes != NULL)
+ {
+ gchar **it;
+
+ for (it = node->element_classes; *it != NULL; it++)
+ hash = hash * 33 + g_str_hash (*it) + 1;
+ }
+
+ if (node->pseudo_classes != NULL)
+ {
+ gchar **it;
+
+ for (it = node->pseudo_classes; *it != NULL; it++)
+ hash = hash * 33 + g_str_hash (*it) + 1;
+ }
+
+ return hash;
+}
+
+static void
+ensure_properties (StThemeNode *node)
+{
+ if (!node->properties_computed)
+ {
+ GPtrArray *properties = NULL;
+
+ node->properties_computed = TRUE;
+
+ if (node->theme)
+ properties = _st_theme_get_matched_properties (node->theme, node);
+
+ if (node->inline_style && *node->inline_style != '\0')
+ {
+ CRDeclaration *cur_decl;
+
+ if (!properties)
+ properties = g_ptr_array_new ();
+
+ node->inline_properties = _st_theme_parse_declaration_list (node->inline_style);
+ for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next)
+ g_ptr_array_add (properties, cur_decl);
+ }
+
+ if (properties)
+ {
+ node->n_properties = properties->len;
+ node->properties = (CRDeclaration **)g_ptr_array_free (properties, FALSE);
+ }
+ }
+}
+
+typedef enum {
+ VALUE_FOUND,
+ VALUE_NOT_FOUND,
+ VALUE_INHERIT
+} GetFromTermResult;
+
+static gboolean
+term_is_inherit (CRTerm *term)
+{
+ return (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "inherit") == 0);
+}
+
+static gboolean
+term_is_none (CRTerm *term)
+{
+ return (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "none") == 0);
+}
+
+static gboolean
+term_is_transparent (CRTerm *term)
+{
+ return (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "transparent") == 0);
+}
+
+static int
+color_component_from_double (double component)
+{
+ /* We want to spread the range 0-1 equally over 0..255, but
+ * 1.0 should map to 255 not 256, so we need to special-case it.
+ * See http://people.redhat.com/otaylor/pixel-converting.html
+ * for (very) detailed discussion of related issues. */
+ if (component >= 1.0)
+ return 255;
+ else
+ return (int)(component * 256);
+}
+
+static GetFromTermResult
+get_color_from_rgba_term (CRTerm *term,
+ ClutterColor *color)
+{
+ CRTerm *arg = term->ext_content.func_param;
+ CRNum *num;
+ double r = 0, g = 0, b = 0, a = 0;
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ double value;
+
+ if (arg == NULL)
+ return VALUE_NOT_FOUND;
+
+ if ((i == 0 && arg->the_operator != NO_OP) ||
+ (i > 0 && arg->the_operator != COMMA))
+ return VALUE_NOT_FOUND;
+
+ if (arg->type != TERM_NUMBER)
+ return VALUE_NOT_FOUND;
+
+ num = arg->content.num;
+
+ /* For simplicity, we convert a,r,g,b to [0,1.0] floats and then
+ * convert them back below. Then when we set them on a cairo content
+ * we convert them back to floats, and then cairo converts them
+ * back to integers to pass them to X, and so forth...
+ */
+ if (i < 3)
+ {
+ if (num->type == NUM_PERCENTAGE)
+ value = num->val / 100;
+ else if (num->type == NUM_GENERIC)
+ value = num->val / 255;
+ else
+ return VALUE_NOT_FOUND;
+ }
+ else
+ {
+ if (num->type != NUM_GENERIC)
+ return VALUE_NOT_FOUND;
+
+ value = num->val;
+ }
+
+ value = CLAMP (value, 0, 1);
+
+ switch (i)
+ {
+ case 0:
+ r = value;
+ break;
+ case 1:
+ g = value;
+ break;
+ case 2:
+ b = value;
+ break;
+ case 3:
+ a = value;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ arg = arg->next;
+ }
+
+ color->red = color_component_from_double (r);
+ color->green = color_component_from_double (g);
+ color->blue = color_component_from_double (b);
+ color->alpha = color_component_from_double (a);
+
+ return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_color_from_term (StThemeNode *node,
+ CRTerm *term,
+ ClutterColor *color)
+{
+ CRRgb rgb;
+ enum CRStatus status;
+
+ if (term_is_inherit (term))
+ {
+ return VALUE_INHERIT;
+ }
+ /* Since libcroco doesn't know about rgba colors, it can't handle
+ * the transparent keyword
+ */
+ else if (term_is_transparent (term))
+ {
+ *color = TRANSPARENT_COLOR;
+ return VALUE_FOUND;
+ }
+ /* rgba () colors - a CSS3 addition, are not supported by libcroco,
+ * but they are parsed as a "function", so we can emulate the
+ * functionality.
+ */
+ else if (term->type == TERM_FUNCTION &&
+ term->content.str &&
+ term->content.str->stryng &&
+ term->content.str->stryng->str &&
+ strcmp (term->content.str->stryng->str, "rgba") == 0)
+ {
+ return get_color_from_rgba_term (term, color);
+ }
+
+ status = cr_rgb_set_from_term (&rgb, term);
+ if (status != CR_OK)
+ return VALUE_NOT_FOUND;
+
+ if (rgb.is_percentage)
+ cr_rgb_compute_from_percentage (&rgb);
+
+ color->red = rgb.red;
+ color->green = rgb.green;
+ color->blue = rgb.blue;
+ color->alpha = 0xff;
+
+ return VALUE_FOUND;
+}
+
+/**
+ * st_theme_node_lookup_color:
+ * @node: a #StThemeNode
+ * @property_name: The name of the color property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @color: (out caller-allocates): location to store the color that was
+ * determined. If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Generically looks up a property containing a single color value. When
+ * specific getters (like st_theme_node_get_background_color()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * See also st_theme_node_get_color(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_color (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ ClutterColor *color)
+{
+
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ GetFromTermResult result = get_color_from_term (node, decl->value, color);
+ if (result == VALUE_FOUND)
+ {
+ return TRUE;
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
+ else
+ break;
+ }
+ }
+ }
+
+ if (inherit && node->parent_node)
+ return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
+
+ return FALSE;
+}
+
+/**
+ * st_theme_node_get_color:
+ * @node: a #StThemeNode
+ * @property_name: The name of the color property
+ * @color: (out caller-allocates): location to store the color that
+ * was determined.
+ *
+ * Generically looks up a property containing a single color value. When
+ * specific getters (like st_theme_node_get_background_color()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * If @property_name is not found, a warning will be logged and a
+ * default color returned.
+ *
+ * See also st_theme_node_lookup_color(), which provides more options,
+ * and lets you handle the case where the theme does not specify the
+ * indicated color.
+ */
+void
+st_theme_node_get_color (StThemeNode *node,
+ const char *property_name,
+ ClutterColor *color)
+{
+ if (!st_theme_node_lookup_color (node, property_name, FALSE, color))
+ {
+ g_warning ("Did not find color property '%s'", property_name);
+ memset (color, 0, sizeof (ClutterColor));
+ }
+}
+
+/**
+ * st_theme_node_lookup_double:
+ * @node: a #StThemeNode
+ * @property_name: The name of the numeric property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @value: (out): location to store the value that was determined.
+ * If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Generically looks up a property containing a single numeric value
+ * without units.
+ *
+ * See also st_theme_node_get_double(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_double (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ double *value)
+{
+ gboolean result = FALSE;
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ CRTerm *term = decl->value;
+
+ if (term->type != TERM_NUMBER || term->content.num->type != NUM_GENERIC)
+ continue;
+
+ *value = term->content.num->val;
+ result = TRUE;
+ break;
+ }
+ }
+
+ if (!result && inherit && node->parent_node)
+ result = st_theme_node_lookup_double (node->parent_node, property_name, inherit, value);
+
+ return result;
+}
+
+/**
+ * st_theme_node_lookup_time:
+ * @node: a #StThemeNode
+ * @property_name: The name of the time property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @value: (out): location to store the value that was determined.
+ * If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Generically looks up a property containing a single time value,
+ * which is converted to milliseconds.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_time (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ double *value)
+{
+ gboolean result = FALSE;
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ CRTerm *term = decl->value;
+ int factor = 1;
+
+ if (term->type != TERM_NUMBER)
+ continue;
+
+ if (term->content.num->type != NUM_TIME_S &&
+ term->content.num->type != NUM_TIME_MS)
+ continue;
+
+ if (term->content.num->type == NUM_TIME_S)
+ factor = 1000;
+
+ *value = factor * term->content.num->val;
+ result = TRUE;
+ break;
+ }
+ }
+
+ if (!result && inherit && node->parent_node)
+ result = st_theme_node_lookup_time (node->parent_node, property_name, inherit, value);
+
+ return result;
+}
+
+/**
+ * st_theme_node_get_double:
+ * @node: a #StThemeNode
+ * @property_name: The name of the numeric property
+ *
+ * Generically looks up a property containing a single numeric value
+ * without units.
+ *
+ * See also st_theme_node_lookup_double(), which provides more options,
+ * and lets you handle the case where the theme does not specify the
+ * indicated value.
+ *
+ * Returns: the value found. If @property_name is not
+ * found, a warning will be logged and 0 will be returned.
+ */
+gdouble
+st_theme_node_get_double (StThemeNode *node,
+ const char *property_name)
+{
+ gdouble value;
+
+ if (st_theme_node_lookup_double (node, property_name, FALSE, &value))
+ return value;
+ else
+ {
+ g_warning ("Did not find double property '%s'", property_name);
+ return 0.0;
+ }
+}
+
+/**
+ * st_theme_node_lookup_url:
+ * @node: a #StThemeNode
+ * @property_name: The name of the string property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @file: (out) (transfer full): location to store the newly allocated value that was
+ * determined. If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Looks up a property containing a single URL value.
+ *
+ * See also st_theme_node_get_url(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_url (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ GFile **file)
+{
+ gboolean result = FALSE;
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ CRTerm *term = decl->value;
+ CRStyleSheet *base_stylesheet;
+
+ if (term->type != TERM_URI && term->type != TERM_STRING)
+ continue;
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ *file = _st_theme_resolve_url (node->theme,
+ base_stylesheet,
+ decl->value->content.str->stryng->str);
+ result = TRUE;
+ break;
+ }
+ }
+
+ if (!result && inherit && node->parent_node)
+ result = st_theme_node_lookup_url (node->parent_node, property_name, inherit, file);
+
+ return result;
+}
+
+/**
+ * st_theme_node_get_url:
+ * @node: a #StThemeNode
+ * @property_name: The name of the string property
+ *
+ * Looks up a property containing a single URL value.
+ *
+ * See also st_theme_node_lookup_url(), which provides more options,
+ * and lets you handle the case where the theme does not specify the
+ * indicated value.
+ *
+ * Returns: (nullable) (transfer full): the newly allocated value if found.
+ * If @property_name is not found, a warning will be logged and %NULL
+ * will be returned.
+ */
+GFile *
+st_theme_node_get_url (StThemeNode *node,
+ const char *property_name)
+{
+ GFile *file;
+
+ if (st_theme_node_lookup_url (node, property_name, FALSE, &file))
+ return file;
+ else
+ {
+ g_warning ("Did not find string property '%s'", property_name);
+ return NULL;
+ }
+}
+
+static const PangoFontDescription *
+get_parent_font (StThemeNode *node)
+{
+ if (node->parent_node)
+ return st_theme_node_get_font (node->parent_node);
+ else
+ return st_theme_context_get_font (node->context);
+}
+
+static GetFromTermResult
+get_length_from_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean use_parent_font,
+ gdouble *length)
+{
+ CRNum *num;
+
+ enum {
+ ABSOLUTE,
+ POINTS,
+ FONT_RELATIVE,
+ } type = ABSOLUTE;
+
+ double multiplier = 1.0;
+
+
+ if (term->type != TERM_NUMBER)
+ {
+ g_warning ("Ignoring length property that isn't a number at line %d, col %d",
+ term->location.line, term->location.column);
+ return VALUE_NOT_FOUND;
+ }
+
+ num = term->content.num;
+
+ switch (num->type)
+ {
+ case NUM_LENGTH_PX:
+ type = ABSOLUTE;
+ multiplier = 1 * node->cached_scale_factor;
+ break;
+ case NUM_LENGTH_PT:
+ type = POINTS;
+ multiplier = 1;
+ break;
+ case NUM_LENGTH_IN:
+ type = POINTS;
+ multiplier = 72;
+ break;
+ case NUM_LENGTH_CM:
+ type = POINTS;
+ multiplier = 72. / 2.54;
+ break;
+ case NUM_LENGTH_MM:
+ type = POINTS;
+ multiplier = 72. / 25.4;
+ break;
+ case NUM_LENGTH_PC:
+ type = POINTS;
+ multiplier = 12. / 25.4;
+ break;
+ case NUM_LENGTH_EM:
+ {
+ type = FONT_RELATIVE;
+ multiplier = 1;
+ break;
+ }
+ case NUM_LENGTH_EX:
+ {
+ /* Doing better would require actually resolving the font description
+ * to a specific font, and Pango doesn't have an ex metric anyways,
+ * so we'd have to try and synthesize it by complicated means.
+ *
+ * The 0.5em is the CSS spec suggested thing to use when nothing
+ * better is available.
+ */
+ type = FONT_RELATIVE;
+ multiplier = 0.5;
+ break;
+ }
+
+ case NUM_INHERIT:
+ return VALUE_INHERIT;
+
+ case NUM_AUTO:
+ g_warning ("'auto' not supported for lengths");
+ return VALUE_NOT_FOUND;
+
+ case NUM_GENERIC:
+ {
+ if (num->val != 0)
+ {
+ g_warning ("length values must specify a unit");
+ return VALUE_NOT_FOUND;
+ }
+ else
+ {
+ type = ABSOLUTE;
+ multiplier = 0;
+ }
+ break;
+ }
+
+ case NUM_PERCENTAGE:
+ g_warning ("percentage lengths not currently supported");
+ return VALUE_NOT_FOUND;
+
+ case NUM_ANGLE_DEG:
+ case NUM_ANGLE_RAD:
+ case NUM_ANGLE_GRAD:
+ case NUM_TIME_MS:
+ case NUM_TIME_S:
+ case NUM_FREQ_HZ:
+ case NUM_FREQ_KHZ:
+ case NUM_UNKNOWN_TYPE:
+ case NB_NUM_TYPE:
+ default:
+ g_warning ("Ignoring invalid type of number of length property");
+ return VALUE_NOT_FOUND;
+ }
+
+ switch (type)
+ {
+ case ABSOLUTE:
+ *length = num->val * multiplier;
+ break;
+ case POINTS:
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ *length = num->val * multiplier * (resolution / 72.);
+ }
+ break;
+ case FONT_RELATIVE:
+ {
+ const PangoFontDescription *desc;
+ double font_size;
+
+ if (use_parent_font)
+ desc = get_parent_font (node);
+ else
+ desc = st_theme_node_get_font (node);
+
+ font_size = (double)pango_font_description_get_size (desc) / PANGO_SCALE;
+
+ if (pango_font_description_get_size_is_absolute (desc))
+ {
+ *length = num->val * multiplier * font_size;
+ }
+ else
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ *length = num->val * multiplier * (resolution / 72.) * font_size;
+ }
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_length_from_term_int (StThemeNode *node,
+ CRTerm *term,
+ gboolean use_parent_font,
+ gint *length)
+{
+ double value;
+ GetFromTermResult result;
+
+ result = get_length_from_term (node, term, use_parent_font, &value);
+ if (result == VALUE_FOUND)
+ *length = (int) ((value / node->cached_scale_factor) + 0.5) * node->cached_scale_factor;
+ return result;
+}
+
+static GetFromTermResult
+get_length_internal (StThemeNode *node,
+ const char *property_name,
+ gdouble *length)
+{
+ int i;
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ GetFromTermResult result = get_length_from_term (node, decl->value, FALSE, length);
+ if (result != VALUE_NOT_FOUND)
+ return result;
+ }
+ }
+
+ return VALUE_NOT_FOUND;
+}
+
+/**
+ * st_theme_node_lookup_length:
+ * @node: a #StThemeNode
+ * @property_name: The name of the length property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @length: (out): location to store the length that was determined.
+ * If the property is not found, the value in this location
+ * will not be changed. The returned length is resolved
+ * to pixels.
+ *
+ * Generically looks up a property containing a single length value. When
+ * specific getters (like st_theme_node_get_border_width()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * See also st_theme_node_get_length(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_length (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ gdouble *length)
+{
+ GetFromTermResult result;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ result = get_length_internal (node, property_name, length);
+
+ if (result == VALUE_FOUND)
+ return TRUE;
+ else if (result == VALUE_INHERIT)
+ inherit = TRUE;
+
+ if (inherit && node->parent_node)
+ return st_theme_node_lookup_length (node->parent_node, property_name, inherit, length);
+
+ return FALSE;
+}
+
+/**
+ * st_theme_node_get_length:
+ * @node: a #StThemeNode
+ * @property_name: The name of the length property
+ *
+ * Generically looks up a property containing a single length value. When
+ * specific getters (like st_theme_node_get_border_width()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * Unlike st_theme_node_get_color() and st_theme_node_get_double(),
+ * this does not print a warning if the property is not found; it just
+ * returns 0.
+ *
+ * See also st_theme_node_lookup_length(), which provides more options. The
+ * returned value is in physical pixels, as opposed to logical pixels.
+ *
+ * Returns: the length, in pixels, or 0 if the property was not found.
+ */
+gdouble
+st_theme_node_get_length (StThemeNode *node,
+ const char *property_name)
+{
+ gdouble length;
+
+ if (st_theme_node_lookup_length (node, property_name, FALSE, &length))
+ return length;
+ else
+ return 0.0;
+}
+
+static void
+do_border_radius_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean topleft,
+ gboolean topright,
+ gboolean bottomright,
+ gboolean bottomleft)
+{
+ int value;
+
+ if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
+ return;
+
+ if (topleft)
+ node->border_radius[ST_CORNER_TOPLEFT] = value;
+ if (topright)
+ node->border_radius[ST_CORNER_TOPRIGHT] = value;
+ if (bottomright)
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] = value;
+ if (bottomleft)
+ node->border_radius[ST_CORNER_BOTTOMLEFT] = value;
+}
+
+static void
+do_border_radius (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 13; /* Skip 'border-radius' */
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Slight deviation ... if we don't understand some of the terms and understand others,
+ * then we set the ones we understand and ignore the others instead of ignoring the
+ * whole thing
+ */
+ if (decl->value == NULL) /* 0 values */
+ return;
+ else if (decl->value->next == NULL) /* 1 value */
+ {
+ do_border_radius_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* all corners */
+ return;
+ }
+ else if (decl->value->next->next == NULL) /* 2 values */
+ {
+ do_border_radius_term (node, decl->value, TRUE, FALSE, TRUE, FALSE); /* topleft/bottomright */
+ do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
+ }
+ else if (decl->value->next->next->next == NULL) /* 3 values */
+ {
+ do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
+ do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
+ do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
+ }
+ else if (decl->value->next->next->next->next == NULL) /* 4 values */
+ {
+ do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
+ do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* topright */
+ do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
+ do_border_radius_term (node, decl->value->next->next->next, FALSE, FALSE, FALSE, TRUE); /* bottomleft */
+ }
+ else
+ {
+ g_warning ("Too many values for border-radius property");
+ return;
+ }
+ }
+ else
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (strcmp (property_name, "-topleft") == 0)
+ do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
+ else if (strcmp (property_name, "-topright") == 0)
+ do_border_radius_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
+ else if (strcmp (property_name, "-bottomright") == 0)
+ do_border_radius_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
+ else if (strcmp (property_name, "-bottomleft") == 0)
+ do_border_radius_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+ }
+}
+
+static void
+do_border_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 6; /* Skip 'border' */
+ StSide side = (StSide)-1;
+ ClutterColor color;
+ gboolean color_set = FALSE;
+ int width = 0; /* suppress warning */
+ gboolean width_set = FALSE;
+ int j;
+
+ if (g_str_has_prefix (property_name, "-radius"))
+ {
+ do_border_radius (node, decl);
+ return;
+ }
+
+ if (g_str_has_prefix (property_name, "-left"))
+ {
+ side = ST_SIDE_LEFT;
+ property_name += 5;
+ }
+ else if (g_str_has_prefix (property_name, "-right"))
+ {
+ side = ST_SIDE_RIGHT;
+ property_name += 6;
+ }
+ else if (g_str_has_prefix (property_name, "-top"))
+ {
+ side = ST_SIDE_TOP;
+ property_name += 4;
+ }
+ else if (g_str_has_prefix (property_name, "-bottom"))
+ {
+ side = ST_SIDE_BOTTOM;
+ property_name += 7;
+ }
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Set value for width/color/style in any order */
+ CRTerm *term;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ GetFromTermResult result;
+
+ if (term->type == TERM_IDENT)
+ {
+ const char *ident = term->content.str->stryng->str;
+ if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
+ {
+ width = 0;
+ width_set = TRUE;
+ continue;
+ }
+ else if (strcmp (ident, "solid") == 0)
+ {
+ /* The only thing we support */
+ continue;
+ }
+ else if (strcmp (ident, "dotted") == 0 ||
+ strcmp (ident, "dashed") == 0 ||
+ strcmp (ident, "double") == 0 ||
+ strcmp (ident, "groove") == 0 ||
+ strcmp (ident, "ridge") == 0 ||
+ strcmp (ident, "inset") == 0 ||
+ strcmp (ident, "outset") == 0)
+ {
+ /* Treat the same as solid */
+ continue;
+ }
+
+ /* Presumably a color, fall through */
+ }
+
+ if (term->type == TERM_NUMBER)
+ {
+ result = get_length_from_term_int (node, term, FALSE, &width);
+ if (result != VALUE_NOT_FOUND)
+ {
+ width_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ result = get_color_from_term (node, term, &color);
+ if (result != VALUE_NOT_FOUND)
+ {
+ color_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ }
+ else if (strcmp (property_name, "-color") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
+ /* Ignore inherit */
+ color_set = TRUE;
+ }
+ else if (strcmp (property_name, "-width") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
+ /* Ignore inherit */
+ width_set = TRUE;
+ }
+
+ if (side == (StSide)-1)
+ {
+ for (j = 0; j < 4; j++)
+ {
+ if (color_set)
+ node->border_color[j] = color;
+ if (width_set)
+ node->border_width[j] = width;
+ }
+ }
+ else
+ {
+ if (color_set)
+ node->border_color[side] = color;
+ if (width_set)
+ node->border_width[side] = width;
+ }
+}
+
+static void
+do_outline_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 7; /* Skip 'outline' */
+ ClutterColor color;
+ gboolean color_set = FALSE;
+ int width = 0; /* suppress warning */
+ gboolean width_set = FALSE;
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Set value for width/color/style in any order */
+ CRTerm *term;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ GetFromTermResult result;
+
+ if (term->type == TERM_IDENT)
+ {
+ const char *ident = term->content.str->stryng->str;
+ if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
+ {
+ width = 0;
+ width_set = TRUE;
+ continue;
+ }
+ else if (strcmp (ident, "solid") == 0)
+ {
+ /* The only thing we support */
+ continue;
+ }
+ else if (strcmp (ident, "dotted") == 0 ||
+ strcmp (ident, "dashed") == 0 ||
+ strcmp (ident, "double") == 0 ||
+ strcmp (ident, "groove") == 0 ||
+ strcmp (ident, "ridge") == 0 ||
+ strcmp (ident, "inset") == 0 ||
+ strcmp (ident, "outset") == 0)
+ {
+ /* Treat the same as solid */
+ continue;
+ }
+
+ /* Presumably a color, fall through */
+ }
+
+ if (term->type == TERM_NUMBER)
+ {
+ result = get_length_from_term_int (node, term, FALSE, &width);
+ if (result != VALUE_NOT_FOUND)
+ {
+ width_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ result = get_color_from_term (node, term, &color);
+ if (result != VALUE_NOT_FOUND)
+ {
+ color_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ }
+ else if (strcmp (property_name, "-color") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
+ /* Ignore inherit */
+ color_set = TRUE;
+ }
+ else if (strcmp (property_name, "-width") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
+ /* Ignore inherit */
+ width_set = TRUE;
+ }
+
+ if (color_set)
+ node->outline_color = color;
+ if (width_set)
+ node->outline_width = width;
+}
+
+static void
+do_padding_property_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean left,
+ gboolean right,
+ gboolean top,
+ gboolean bottom)
+{
+ int value;
+
+ if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
+ return;
+
+ if (left)
+ node->padding[ST_SIDE_LEFT] = value;
+ if (right)
+ node->padding[ST_SIDE_RIGHT] = value;
+ if (top)
+ node->padding[ST_SIDE_TOP] = value;
+ if (bottom)
+ node->padding[ST_SIDE_BOTTOM] = value;
+}
+
+static void
+do_padding_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 7; /* Skip 'padding' */
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Slight deviation ... if we don't understand some of the terms and understand others,
+ * then we set the ones we understand and ignore the others instead of ignoring the
+ * whole thing
+ */
+ if (decl->value == NULL) /* 0 values */
+ return;
+ else if (decl->value->next == NULL) /* 1 value */
+ {
+ do_padding_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
+ return;
+ }
+ else if (decl->value->next->next == NULL) /* 2 values */
+ {
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, TRUE); /* top/bottom */
+ do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ }
+ else if (decl->value->next->next->next == NULL) /* 3 values */
+ {
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ }
+ else if (decl->value->next->next->next->next == NULL) /* 4 values */
+ {
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_padding_property_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* right */
+ do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ do_padding_property_term (node, decl->value->next->next->next, TRUE, FALSE, FALSE, FALSE); /* left */
+ }
+ else
+ {
+ g_warning ("Too many values for padding property");
+ return;
+ }
+ }
+ else
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (strcmp (property_name, "-left") == 0)
+ do_padding_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
+ else if (strcmp (property_name, "-right") == 0)
+ do_padding_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
+ else if (strcmp (property_name, "-top") == 0)
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
+ else if (strcmp (property_name, "-bottom") == 0)
+ do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+ }
+}
+
+static void
+do_margin_property_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean left,
+ gboolean right,
+ gboolean top,
+ gboolean bottom)
+{
+ int value;
+
+ if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
+ return;
+
+ if (left)
+ node->margin[ST_SIDE_LEFT] = value;
+ if (right)
+ node->margin[ST_SIDE_RIGHT] = value;
+ if (top)
+ node->margin[ST_SIDE_TOP] = value;
+ if (bottom)
+ node->margin[ST_SIDE_BOTTOM] = value;
+}
+
+static void
+do_margin_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 6; /* Skip 'margin' */
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Slight deviation ... if we don't understand some of the terms and understand others,
+ * then we set the ones we understand and ignore the others instead of ignoring the
+ * whole thing
+ */
+ if (decl->value == NULL) /* 0 values */
+ return;
+ else if (decl->value->next == NULL) /* 1 value */
+ {
+ do_margin_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
+ return;
+ }
+ else if (decl->value->next->next == NULL) /* 2 values */
+ {
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, TRUE); /* top/bottom */
+ do_margin_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ }
+ else if (decl->value->next->next->next == NULL) /* 3 values */
+ {
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_margin_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ do_margin_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ }
+ else if (decl->value->next->next->next->next == NULL) /* 4 values */
+ {
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_margin_property_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* right */
+ do_margin_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ do_margin_property_term (node, decl->value->next->next->next, TRUE, FALSE, FALSE, FALSE); /* left */
+ }
+ else
+ {
+ g_warning ("Too many values for margin property");
+ return;
+ }
+ }
+ else
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (strcmp (property_name, "-left") == 0)
+ do_margin_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
+ else if (strcmp (property_name, "-right") == 0)
+ do_margin_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
+ else if (strcmp (property_name, "-top") == 0)
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
+ else if (strcmp (property_name, "-bottom") == 0)
+ do_margin_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+ }
+}
+
+static void
+do_size_property (StThemeNode *node,
+ CRDeclaration *decl,
+ int *node_value)
+{
+ CRTerm *term = decl->value;
+
+ if (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "auto") == 0)
+ *node_value = -1;
+ else
+ get_length_from_term_int (node, term, FALSE, node_value);
+}
+
+void
+_st_theme_node_ensure_geometry (StThemeNode *node)
+{
+ int i, j;
+ int width, height;
+
+ if (node->geometry_computed)
+ return;
+
+ node->geometry_computed = TRUE;
+
+ ensure_properties (node);
+
+ for (j = 0; j < 4; j++)
+ {
+ node->border_width[j] = 0;
+ node->border_color[j] = TRANSPARENT_COLOR;
+ }
+
+ node->outline_width = 0;
+ node->outline_color = TRANSPARENT_COLOR;
+
+ width = -1;
+ height = -1;
+ node->width = -1;
+ node->height = -1;
+ node->min_width = -1;
+ node->min_height = -1;
+ node->max_width = -1;
+ node->max_height = -1;
+
+ for (i = 0; i < node->n_properties; i++)
+ {
+ CRDeclaration *decl = node->properties[i];
+ const char *property_name = decl->property->stryng->str;
+
+ if (g_str_has_prefix (property_name, "border"))
+ do_border_property (node, decl);
+ else if (g_str_has_prefix (property_name, "outline"))
+ do_outline_property (node, decl);
+ else if (g_str_has_prefix (property_name, "padding"))
+ do_padding_property (node, decl);
+ else if (g_str_has_prefix (property_name, "margin"))
+ do_margin_property (node, decl);
+ else if (strcmp (property_name, "width") == 0)
+ do_size_property (node, decl, &width);
+ else if (strcmp (property_name, "height") == 0)
+ do_size_property (node, decl, &height);
+ else if (strcmp (property_name, "-st-natural-width") == 0)
+ do_size_property (node, decl, &node->width);
+ else if (strcmp (property_name, "-st-natural-height") == 0)
+ do_size_property (node, decl, &node->height);
+ else if (strcmp (property_name, "min-width") == 0)
+ do_size_property (node, decl, &node->min_width);
+ else if (strcmp (property_name, "min-height") == 0)
+ do_size_property (node, decl, &node->min_height);
+ else if (strcmp (property_name, "max-width") == 0)
+ do_size_property (node, decl, &node->max_width);
+ else if (strcmp (property_name, "max-height") == 0)
+ do_size_property (node, decl, &node->max_height);
+ }
+
+ /*
+ * Setting width sets max-width, min-width and -st-natural-width,
+ * unless one of them is set individually.
+ * Setting min-width sets natural width too, so that the minimum
+ * width reported by get_preferred_width() is always not greater
+ * than the natural width.
+ * The natural width in node->width is actually a lower bound, the
+ * actor is allowed to request something greater than that, but
+ * not greater than max-width.
+ * We don't need to clamp node->width to be less than max_width,
+ * that's done by adjust_preferred_width.
+ */
+ if (width != -1)
+ {
+ if (node->width == -1)
+ node->width = width;
+ if (node->min_width == -1)
+ node->min_width = width;
+ if (node->max_width == -1)
+ node->max_width = width;
+ }
+
+ if (node->width < node->min_width)
+ node->width = node->min_width;
+
+ if (height != -1)
+ {
+ if (node->height == -1)
+ node->height = height;
+ if (node->min_height == -1)
+ node->min_height = height;
+ if (node->max_height == -1)
+ node->max_height = height;
+ }
+
+ if (node->height < node->min_height)
+ node->height = node->min_height;
+}
+
+/**
+ * st_theme_node_get_border_width:
+ * @node: a #StThemeNode
+ * @side: a #StCorner
+ *
+ * Get the border width for @node on @side, in physical pixels.
+ *
+ * Returns: the border width in physical pixels
+ */
+int
+st_theme_node_get_border_width (StThemeNode *node,
+ StSide side)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->border_width[side];
+}
+
+/**
+ * st_theme_node_get_border_radius:
+ * @node: a #StThemeNode
+ * @corner: a #StCorner
+ *
+ * Get the border radius for @node at @corner, in physical pixels.
+ *
+ * Returns: the border radius in physical pixels
+ */
+int
+st_theme_node_get_border_radius (StThemeNode *node,
+ StCorner corner)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (corner >= ST_CORNER_TOPLEFT && corner <= ST_CORNER_BOTTOMLEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->border_radius[corner];
+}
+
+/**
+ * st_theme_node_get_outline_width:
+ * @node: a #StThemeNode
+ *
+ * Get the width of the outline for @node, in physical pixels.
+ *
+ * Returns: the width in physical pixels
+ */
+int
+st_theme_node_get_outline_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->outline_width;
+}
+
+/**
+ * st_theme_node_get_outline_color:
+ * @node: a #StThemeNode
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets the color of @node's outline.
+ */
+void
+st_theme_node_get_outline_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ *color = node->outline_color;
+}
+
+/**
+ * st_theme_node_get_width:
+ * @node: a #StThemeNode
+ *
+ * Get the width for @node, in physical pixels.
+ *
+ * Returns: the width in physical pixels
+ */
+int
+st_theme_node_get_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->width;
+}
+
+/**
+ * st_theme_node_get_height:
+ * @node: a #StThemeNode
+ *
+ * Get the height for @node, in physical pixels.
+ *
+ * Returns: the height in physical pixels
+ */
+int
+st_theme_node_get_height (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->height;
+}
+
+/**
+ * st_theme_node_get_min_width:
+ * @node: a #StThemeNode
+ *
+ * Get the minimum width for @node, in physical pixels.
+ *
+ * Returns: the minimum width in physical pixels
+ */
+int
+st_theme_node_get_min_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->min_width;
+}
+
+/**
+ * st_theme_node_get_min_height:
+ * @node: a #StThemeNode
+ *
+ * Get the minimum height for @node, in physical pixels.
+ *
+ * Returns: the minimum height in physical pixels
+ */
+int
+st_theme_node_get_min_height (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->min_height;
+}
+
+/**
+ * st_theme_node_get_max_width:
+ * @node: a #StThemeNode
+ *
+ * Get the maximum width for @node, in physical pixels.
+ *
+ * Returns: the maximum width in physical pixels
+ */
+int
+st_theme_node_get_max_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->max_width;
+}
+
+/**
+ * st_theme_node_get_max_height:
+ * @node: a #StThemeNode
+ *
+ * Get the maximum height for @node, in physical pixels.
+ *
+ * Returns: the maximum height in physical pixels
+ */
+int
+st_theme_node_get_max_height (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->max_height;
+}
+
+void
+_st_theme_node_ensure_background (StThemeNode *node)
+{
+ int i;
+
+ if (node->background_computed)
+ return;
+
+ node->background_repeat = FALSE;
+ node->background_computed = TRUE;
+ node->background_color = TRANSPARENT_COLOR;
+ node->background_gradient_type = ST_GRADIENT_NONE;
+ node->background_position_set = FALSE;
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+
+ ensure_properties (node);
+
+ for (i = 0; i < node->n_properties; i++)
+ {
+ CRDeclaration *decl = node->properties[i];
+ const char *property_name = decl->property->stryng->str;
+
+ if (g_str_has_prefix (property_name, "background"))
+ property_name += 10;
+ else
+ continue;
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* We're very liberal here ... if we recognize any term in the expression we take it, and
+ * we ignore the rest. The actual specification is:
+ *
+ * background: [<'background-color'> || <'background-image'> || <'background-repeat'> || <'background-attachment'> || <'background-position'>] | inherit
+ */
+
+ CRTerm *term;
+ /* background: property sets all terms to specified or default values */
+ node->background_color = TRANSPARENT_COLOR;
+ g_clear_object (&node->background_image);
+ node->background_position_set = FALSE;
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ GetFromTermResult result = get_color_from_term (node, term, &node->background_color);
+ if (result == VALUE_FOUND)
+ {
+ /* color stored in node->background_color */
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ {
+ st_theme_node_get_background_color (node->parent_node, &node->background_color);
+ node->background_image = g_object_ref (st_theme_node_get_background_image (node->parent_node));
+ }
+ }
+ else if (term_is_none (term))
+ {
+ /* leave node->background_color as transparent */
+ }
+ else if (term->type == TERM_URI)
+ {
+ CRStyleSheet *base_stylesheet;
+ GFile *file;
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ file = _st_theme_resolve_url (node->theme,
+ base_stylesheet,
+ term->content.str->stryng->str);
+
+ node->background_image = file;
+ }
+ }
+ }
+ else if (strcmp (property_name, "-position") == 0)
+ {
+ GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_position_x);
+ if (result == VALUE_NOT_FOUND)
+ {
+ node->background_position_set = FALSE;
+ continue;
+ }
+ else
+ node->background_position_set = TRUE;
+
+ result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_position_y);
+
+ if (result == VALUE_NOT_FOUND)
+ {
+ node->background_position_set = FALSE;
+ continue;
+ }
+ else
+ node->background_position_set = TRUE;
+ }
+ else if (strcmp (property_name, "-repeat") == 0)
+ {
+ if (decl->value->type == TERM_IDENT)
+ {
+ if (strcmp (decl->value->content.str->stryng->str, "repeat") == 0)
+ node->background_repeat = TRUE;
+ }
+ }
+ else if (strcmp (property_name, "-size") == 0)
+ {
+ if (decl->value->type == TERM_IDENT)
+ {
+ if (strcmp (decl->value->content.str->stryng->str, "contain") == 0)
+ node->background_size = ST_BACKGROUND_SIZE_CONTAIN;
+ else if (strcmp (decl->value->content.str->stryng->str, "cover") == 0)
+ node->background_size = ST_BACKGROUND_SIZE_COVER;
+ else if ((strcmp (decl->value->content.str->stryng->str, "auto") == 0) && (decl->value->next) && (decl->value->next->type == TERM_NUMBER))
+ {
+ GetFromTermResult result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
+
+ node->background_size_w = -1;
+ node->background_size = (result == VALUE_FOUND) ? ST_BACKGROUND_SIZE_FIXED : ST_BACKGROUND_SIZE_AUTO;
+ }
+ else
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+ }
+ else if (decl->value->type == TERM_NUMBER)
+ {
+ GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_size_w);
+ if (result == VALUE_NOT_FOUND)
+ continue;
+
+ node->background_size = ST_BACKGROUND_SIZE_FIXED;
+
+ if ((decl->value->next) && (decl->value->next->type == TERM_NUMBER))
+ {
+ result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
+
+ if (result == VALUE_FOUND)
+ continue;
+ }
+ node->background_size_h = -1;
+ }
+ else
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+ }
+ else if (strcmp (property_name, "-color") == 0)
+ {
+ GetFromTermResult result;
+
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ result = get_color_from_term (node, decl->value, &node->background_color);
+ if (result == VALUE_FOUND)
+ {
+ /* color stored in node->background_color */
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ st_theme_node_get_background_color (node->parent_node, &node->background_color);
+ }
+ }
+ else if (strcmp (property_name, "-image") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (decl->value->type == TERM_URI)
+ {
+ CRStyleSheet *base_stylesheet;
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ g_clear_object (&node->background_image);
+ node->background_image = _st_theme_resolve_url (node->theme,
+ base_stylesheet,
+ decl->value->content.str->stryng->str);
+ }
+ else if (term_is_inherit (decl->value))
+ {
+ g_clear_object (&node->background_image);
+ node->background_image = g_object_ref (st_theme_node_get_background_image (node->parent_node));
+ }
+ else if (term_is_none (decl->value))
+ {
+ g_clear_object (&node->background_image);
+ }
+ }
+ else if (strcmp (property_name, "-gradient-direction") == 0)
+ {
+ CRTerm *term = decl->value;
+ if (strcmp (term->content.str->stryng->str, "vertical") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_VERTICAL;
+ }
+ else if (strcmp (term->content.str->stryng->str, "horizontal") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_HORIZONTAL;
+ }
+ else if (strcmp (term->content.str->stryng->str, "radial") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_RADIAL;
+ }
+ else if (strcmp (term->content.str->stryng->str, "none") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_NONE;
+ }
+ else
+ {
+ g_warning ("Unrecognized background-gradient-direction \"%s\"",
+ term->content.str->stryng->str);
+ }
+ }
+ else if (strcmp (property_name, "-gradient-start") == 0)
+ {
+ get_color_from_term (node, decl->value, &node->background_color);
+ }
+ else if (strcmp (property_name, "-gradient-end") == 0)
+ {
+ get_color_from_term (node, decl->value, &node->background_gradient_end);
+ }
+ }
+}
+
+/**
+ * st_theme_node_get_background_color:
+ * @node: a #StThemeNode
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets @node's background color.
+ */
+void
+st_theme_node_get_background_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_background (node);
+
+ *color = node->background_color;
+}
+
+/**
+ * st_theme_node_get_background_image:
+ * @node: a #StThemeNode
+ *
+ * Returns: (transfer none): @node's background image.
+ */
+GFile *
+st_theme_node_get_background_image (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ _st_theme_node_ensure_background (node);
+
+ return node->background_image;
+}
+
+/**
+ * st_theme_node_get_foreground_color:
+ * @node: a #StThemeNode
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets @node's foreground color.
+ */
+void
+st_theme_node_get_foreground_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ if (!node->foreground_computed)
+ {
+ int i;
+
+ node->foreground_computed = TRUE;
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "color") == 0)
+ {
+ GetFromTermResult result = get_color_from_term (node, decl->value, &node->foreground_color);
+ if (result == VALUE_FOUND)
+ goto out;
+ else if (result == VALUE_INHERIT)
+ break;
+ }
+ }
+
+ if (node->parent_node)
+ st_theme_node_get_foreground_color (node->parent_node, &node->foreground_color);
+ else
+ node->foreground_color = BLACK_COLOR; /* default to black */
+ }
+
+ out:
+ *color = node->foreground_color;
+}
+
+
+/**
+ * st_theme_node_get_background_gradient:
+ * @node: A #StThemeNode
+ * @type: (out): Type of gradient
+ * @start: (out caller-allocates): Color at start of gradient
+ * @end: (out caller-allocates): Color at end of gradient
+ *
+ * The @start and @end arguments will only be set if @type is not #ST_GRADIENT_NONE.
+ */
+void
+st_theme_node_get_background_gradient (StThemeNode *node,
+ StGradientType *type,
+ ClutterColor *start,
+ ClutterColor *end)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_background (node);
+
+ *type = node->background_gradient_type;
+ if (*type != ST_GRADIENT_NONE)
+ {
+ *start = node->background_color;
+ *end = node->background_gradient_end;
+ }
+}
+
+/**
+ * st_theme_node_get_border_color:
+ * @node: a #StThemeNode
+ * @side: a #StSide
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets the color of @node's border on @side
+ */
+void
+st_theme_node_get_border_color (StThemeNode *node,
+ StSide side,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT);
+
+ _st_theme_node_ensure_geometry (node);
+
+ *color = node->border_color[side];
+}
+
+/**
+ * st_theme_node_get_padding:
+ * @node: a #StThemeNode
+ * @side: a #StSide
+ *
+ * Get the padding for @node on @side, in physical pixels. This corresponds to
+ * the CSS properties such as `padding-top`.
+ *
+ * Returns: the padding size in physical pixels
+ */
+double
+st_theme_node_get_padding (StThemeNode *node,
+ StSide side)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->padding[side];
+}
+
+/**
+ * st_theme_node_get_margin:
+ * @node: a #StThemeNode
+ * @side: a #StSide
+ *
+ * Get the margin for @node on @side, in physical pixels. This corresponds to
+ * the CSS properties such as `margin-top`.
+ *
+ * Returns: the margin size in physical pixels
+ */
+double
+st_theme_node_get_margin (StThemeNode *node,
+ StSide side)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->margin[side];
+}
+
+/**
+ * st_theme_node_get_transition_duration:
+ * @node: an #StThemeNode
+ *
+ * Get the value of the transition-duration property, which
+ * specifies the transition time between the previous #StThemeNode
+ * and @node.
+ *
+ * Returns: the node's transition duration in milliseconds
+ */
+int
+st_theme_node_get_transition_duration (StThemeNode *node)
+{
+ StSettings *settings;
+ gdouble value = 0.0;
+ gdouble factor;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
+
+ settings = st_settings_get ();
+ g_object_get (settings, "slow-down-factor", &factor, NULL);
+
+ if (node->transition_duration > -1)
+ return factor * node->transition_duration;
+
+ st_theme_node_lookup_time (node, "transition-duration", FALSE, &value);
+
+ node->transition_duration = (int)value;
+
+ return factor * node->transition_duration;
+}
+
+/**
+ * st_theme_node_get_icon_style:
+ * @node: a #StThemeNode
+ *
+ * Get the icon style for @node (eg. symbolic, regular). This corresponds to the
+ * special `-st-icon-style` CSS property.
+ *
+ * Returns: the icon style for @node
+ */
+StIconStyle
+st_theme_node_get_icon_style (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), ST_ICON_STYLE_REQUESTED);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "-st-icon-style") == 0)
+ {
+ CRTerm *term;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ if (term->type != TERM_IDENT)
+ goto next_decl;
+
+ if (strcmp (term->content.str->stryng->str, "requested") == 0)
+ return ST_ICON_STYLE_REQUESTED;
+ else if (strcmp (term->content.str->stryng->str, "regular") == 0)
+ return ST_ICON_STYLE_REGULAR;
+ else if (strcmp (term->content.str->stryng->str, "symbolic") == 0)
+ return ST_ICON_STYLE_SYMBOLIC;
+ else
+ g_warning ("Unknown -st-icon-style \"%s\"",
+ term->content.str->stryng->str);
+ }
+ }
+
+ next_decl:
+ ;
+ }
+
+ if (node->parent_node)
+ return st_theme_node_get_icon_style (node->parent_node);
+
+ return ST_ICON_STYLE_REQUESTED;
+}
+
+/**
+ * st_theme_node_get_text_decoration
+ * @node: a #StThemeNode
+ *
+ * Get the text decoration for @node (eg. underline, line-through, etc).
+ *
+ * Returns: the text decoration for @node
+ */
+StTextDecoration
+st_theme_node_get_text_decoration (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), 0);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "text-decoration") == 0)
+ {
+ CRTerm *term = decl->value;
+ StTextDecoration decoration = 0;
+
+ /* Specification is none | [ underline || overline || line-through || blink ] | inherit
+ *
+ * We're a bit more liberal, and for example treat 'underline none' as the same as
+ * none.
+ */
+ for (; term; term = term->next)
+ {
+ if (term->type != TERM_IDENT)
+ goto next_decl;
+
+ if (strcmp (term->content.str->stryng->str, "none") == 0)
+ {
+ return 0;
+ }
+ else if (strcmp (term->content.str->stryng->str, "inherit") == 0)
+ {
+ if (node->parent_node)
+ return st_theme_node_get_text_decoration (node->parent_node);
+ }
+ else if (strcmp (term->content.str->stryng->str, "underline") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_UNDERLINE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "overline") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_OVERLINE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "line-through") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_LINE_THROUGH;
+ }
+ else if (strcmp (term->content.str->stryng->str, "blink") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_BLINK;
+ }
+ else
+ {
+ goto next_decl;
+ }
+ }
+
+ return decoration;
+ }
+
+ next_decl:
+ ;
+ }
+
+ return 0;
+}
+
+/**
+ * st_theme_node_get_text_align:
+ * @node: a #StThemeNode
+ *
+ * Get the text alignment of @node.
+ *
+ * Returns: the alignment of text for @node
+ */
+StTextAlign
+st_theme_node_get_text_align(StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), ST_TEXT_ALIGN_LEFT);
+
+ ensure_properties(node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp(decl->property->stryng->str, "text-align") == 0)
+ {
+ CRTerm *term = decl->value;
+
+ if (term->type != TERM_IDENT || term->next)
+ continue;
+
+ if (strcmp(term->content.str->stryng->str, "inherit") == 0)
+ {
+ if (node->parent_node)
+ return st_theme_node_get_text_align(node->parent_node);
+ return ST_TEXT_ALIGN_LEFT;
+ }
+ else if (strcmp(term->content.str->stryng->str, "left") == 0)
+ {
+ return ST_TEXT_ALIGN_LEFT;
+ }
+ else if (strcmp(term->content.str->stryng->str, "right") == 0)
+ {
+ return ST_TEXT_ALIGN_RIGHT;
+ }
+ else if (strcmp(term->content.str->stryng->str, "center") == 0)
+ {
+ return ST_TEXT_ALIGN_CENTER;
+ }
+ else if (strcmp(term->content.str->stryng->str, "justify") == 0)
+ {
+ return ST_TEXT_ALIGN_JUSTIFY;
+ }
+ }
+ }
+ if(node->parent_node)
+ return st_theme_node_get_text_align(node->parent_node);
+
+ if (clutter_get_default_text_direction () == CLUTTER_TEXT_DIRECTION_RTL)
+ return ST_TEXT_ALIGN_RIGHT;
+ return ST_TEXT_ALIGN_LEFT;
+}
+
+/**
+ * st_theme_node_get_letter_spacing:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the letter-spacing style property, in physical pixels.
+ *
+ * Returns: the value of the letter-spacing property, if
+ * found, or zero if such property has not been found.
+ */
+gdouble
+st_theme_node_get_letter_spacing (StThemeNode *node)
+{
+ gdouble spacing = 0.;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), spacing);
+
+ ensure_properties (node);
+
+ st_theme_node_lookup_length (node, "letter-spacing", FALSE, &spacing);
+ return spacing;
+}
+
+static gboolean
+font_family_from_terms (CRTerm *term,
+ char **family)
+{
+ GString *family_string;
+ gboolean result = FALSE;
+ gboolean last_was_quoted = FALSE;
+
+ if (!term)
+ return FALSE;
+
+ family_string = g_string_new (NULL);
+
+ while (term)
+ {
+ if (term->type != TERM_STRING && term->type != TERM_IDENT)
+ {
+ goto out;
+ }
+
+ if (family_string->len > 0)
+ {
+ if (term->the_operator != COMMA && term->the_operator != NO_OP)
+ goto out;
+ /* Can concatenate two bare words, but not two quoted strings */
+ if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
+ goto out;
+
+ if (term->the_operator == NO_OP)
+ g_string_append (family_string, " ");
+ else
+ g_string_append (family_string, ",");
+ }
+ else
+ {
+ if (term->the_operator != NO_OP)
+ goto out;
+ }
+
+ g_string_append (family_string, term->content.str->stryng->str);
+
+ term = term->next;
+ }
+
+ result = TRUE;
+
+ out:
+ if (result)
+ {
+ *family = g_string_free (family_string, FALSE);
+ return TRUE;
+ }
+ else
+ {
+ *family = g_string_free (family_string, TRUE);
+ return FALSE;
+ }
+}
+
+/* In points */
+static int font_sizes[] = {
+ 6 * 1024, /* xx-small */
+ 8 * 1024, /* x-small */
+ 10 * 1024, /* small */
+ 12 * 1024, /* medium */
+ 16 * 1024, /* large */
+ 20 * 1024, /* x-large */
+ 24 * 1024, /* xx-large */
+};
+
+static gboolean
+font_size_from_term (StThemeNode *node,
+ CRTerm *term,
+ double *size)
+{
+ if (term->type == TERM_IDENT)
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ /* We work in integers to avoid double comparisons when converting back
+ * from a size in pixels to a logical size.
+ */
+ int size_points = (int)(0.5 + *size * (72. / resolution));
+
+ if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
+ size_points = font_sizes[0];
+ else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
+ size_points = font_sizes[1];
+ else if (strcmp (term->content.str->stryng->str, "small") == 0)
+ size_points = font_sizes[2];
+ else if (strcmp (term->content.str->stryng->str, "medium") == 0)
+ size_points = font_sizes[3];
+ else if (strcmp (term->content.str->stryng->str, "large") == 0)
+ size_points = font_sizes[4];
+ else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
+ size_points = font_sizes[5];
+ else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
+ size_points = font_sizes[6];
+ else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
+ {
+ /* Find the standard size equal to or smaller than the current size */
+ int i = 0;
+
+ while (i <= 6 && font_sizes[i] < size_points)
+ i++;
+
+ if (i > 6)
+ {
+ /* original size greater than any standard size */
+ size_points = (int)(0.5 + size_points / 1.2);
+ }
+ else
+ {
+ /* Go one smaller than that, if possible */
+ if (i > 0)
+ i--;
+
+ size_points = font_sizes[i];
+ }
+ }
+ else if (strcmp (term->content.str->stryng->str, "larger") == 0)
+ {
+ /* Find the standard size equal to or larger than the current size */
+ int i = 6;
+
+ while (i >= 0 && font_sizes[i] > size_points)
+ i--;
+
+ if (i < 0) /* original size smaller than any standard size */
+ i = 0;
+
+ /* Go one larger than that, if possible */
+ if (i < 6)
+ i++;
+
+ size_points = font_sizes[i];
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ *size = size_points * (resolution / 72.);
+ return TRUE;
+
+ }
+ else if (term->type == TERM_NUMBER && term->content.num->type == NUM_PERCENTAGE)
+ {
+ *size *= term->content.num->val / 100.;
+ return TRUE;
+ }
+ else if (get_length_from_term (node, term, TRUE, size) == VALUE_FOUND)
+ {
+ /* Convert from pixels to Pango units */
+ *size *= 1024;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+font_weight_from_term (CRTerm *term,
+ PangoWeight *weight,
+ gboolean *weight_absolute)
+{
+ if (term->type == TERM_NUMBER)
+ {
+ int weight_int;
+
+ /* The spec only allows numeric weights from 100-900, though Pango
+ * will handle any number. We just let anything through.
+ */
+ if (term->content.num->type != NUM_GENERIC)
+ return FALSE;
+
+ weight_int = (int)(0.5 + term->content.num->val);
+
+ *weight = weight_int;
+ *weight_absolute = TRUE;
+
+ }
+ else if (term->type == TERM_IDENT)
+ {
+ /* FIXME: handle INHERIT */
+
+ if (strcmp (term->content.str->stryng->str, "bold") == 0)
+ {
+ *weight = PANGO_WEIGHT_BOLD;
+ *weight_absolute = TRUE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "normal") == 0)
+ {
+ *weight = PANGO_WEIGHT_NORMAL;
+ *weight_absolute = TRUE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "bolder") == 0)
+ {
+ *weight = PANGO_WEIGHT_BOLD;
+ *weight_absolute = FALSE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "lighter") == 0)
+ {
+ *weight = PANGO_WEIGHT_LIGHT;
+ *weight_absolute = FALSE;
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+font_style_from_term (CRTerm *term,
+ PangoStyle *style)
+{
+ if (term->type != TERM_IDENT)
+ return FALSE;
+
+ /* FIXME: handle INHERIT */
+
+ if (strcmp (term->content.str->stryng->str, "normal") == 0)
+ *style = PANGO_STYLE_NORMAL;
+ else if (strcmp (term->content.str->stryng->str, "oblique") == 0)
+ *style = PANGO_STYLE_OBLIQUE;
+ else if (strcmp (term->content.str->stryng->str, "italic") == 0)
+ *style = PANGO_STYLE_ITALIC;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+font_variant_from_term (CRTerm *term,
+ PangoVariant *variant)
+{
+ if (term->type != TERM_IDENT)
+ return FALSE;
+
+ /* FIXME: handle INHERIT */
+
+ if (strcmp (term->content.str->stryng->str, "normal") == 0)
+ *variant = PANGO_VARIANT_NORMAL;
+ else if (strcmp (term->content.str->stryng->str, "small-caps") == 0)
+ *variant = PANGO_VARIANT_SMALL_CAPS;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_get_font:
+ * @node: a #StThemeNode
+ *
+ * Get the current font of @node as a #PangoFontDescription
+ *
+ * Returns: (transfer none): the current font
+ */
+const PangoFontDescription *
+st_theme_node_get_font (StThemeNode *node)
+{
+ /* Initialized despite _set flags to suppress compiler warnings */
+ PangoStyle font_style = PANGO_STYLE_NORMAL;
+ gboolean font_style_set = FALSE;
+ PangoVariant variant = PANGO_VARIANT_NORMAL;
+ gboolean variant_set = FALSE;
+ PangoWeight weight = PANGO_WEIGHT_NORMAL;
+ gboolean weight_absolute = TRUE;
+ gboolean weight_set = FALSE;
+ double size = 0.;
+ gboolean size_set = FALSE;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ char *family = NULL;
+ double parent_size;
+ int i;
+
+ if (node->font_desc)
+ return node->font_desc;
+
+ node->font_desc = pango_font_description_copy (get_parent_font (node));
+ parent_size = pango_font_description_get_size (node->font_desc);
+ if (!pango_font_description_get_size_is_absolute (node->font_desc))
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ parent_size *= (resolution / 72.);
+ }
+
+ ensure_properties (node);
+
+ for (i = 0; i < node->n_properties; i++)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "font") == 0)
+ {
+ PangoStyle tmp_style = PANGO_STYLE_NORMAL;
+ PangoVariant tmp_variant = PANGO_VARIANT_NORMAL;
+ PangoWeight tmp_weight = PANGO_WEIGHT_NORMAL;
+ gboolean tmp_weight_absolute = TRUE;
+ double tmp_size;
+ CRTerm *term = decl->value;
+
+ /* A font specification starts with node/variant/weight
+ * in any order. Each is allowed to be specified only once,
+ * but we don't enforce that.
+ */
+ for (; term; term = term->next)
+ {
+ if (font_style_from_term (term, &tmp_style))
+ continue;
+ if (font_variant_from_term (term, &tmp_variant))
+ continue;
+ if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
+ continue;
+
+ break;
+ }
+
+ /* The size is mandatory */
+
+ if (term == NULL || term->type != TERM_NUMBER)
+ {
+ g_warning ("Size missing from font property");
+ continue;
+ }
+
+ tmp_size = parent_size;
+ if (!font_size_from_term (node, term, &tmp_size))
+ {
+ g_warning ("Couldn't parse size in font property");
+ continue;
+ }
+
+ term = term->next;
+
+ if (term != NULL && term->type && TERM_NUMBER && term->the_operator == DIVIDE)
+ {
+ /* Ignore line-height specification */
+ term = term->next;
+ }
+
+ /* the font family is mandatory - it is a comma-separated list of
+ * names.
+ */
+ if (!font_family_from_terms (term, &family))
+ {
+ g_warning ("Couldn't parse family in font property");
+ continue;
+ }
+
+ font_style = tmp_style;
+ font_style_set = TRUE;
+ weight = tmp_weight;
+ weight_absolute = tmp_weight_absolute;
+ weight_set = TRUE;
+ variant = tmp_variant;
+ variant_set = TRUE;
+
+ size = tmp_size;
+ size_set = TRUE;
+
+ }
+ else if (strcmp (decl->property->stryng->str, "font-family") == 0)
+ {
+ if (!font_family_from_terms (decl->value, &family))
+ {
+ g_warning ("Couldn't parse family in font property");
+ continue;
+ }
+ }
+ else if (strcmp (decl->property->stryng->str, "font-weight") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (font_weight_from_term (decl->value, &weight, &weight_absolute))
+ weight_set = TRUE;
+ }
+ else if (strcmp (decl->property->stryng->str, "font-style") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (font_style_from_term (decl->value, &font_style))
+ font_style_set = TRUE;
+ }
+ else if (strcmp (decl->property->stryng->str, "font-variant") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (font_variant_from_term (decl->value, &variant))
+ variant_set = TRUE;
+ }
+ else if (strcmp (decl->property->stryng->str, "font-size") == 0)
+ {
+ gdouble tmp_size;
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ tmp_size = parent_size;
+ if (font_size_from_term (node, decl->value, &tmp_size))
+ {
+ size = tmp_size;
+ size_set = TRUE;
+ }
+ }
+ }
+
+ if (family)
+ {
+ pango_font_description_set_family (node->font_desc, family);
+ g_free (family);
+ }
+
+ if (size_set)
+ pango_font_description_set_absolute_size (node->font_desc, size);
+
+ if (weight_set)
+ {
+ if (!weight_absolute)
+ {
+ /* bolder/lighter are supposed to switch between available styles, but with
+ * font substitution, that gets to be a pretty fuzzy concept. So we use
+ * a fixed step of 200. (The spec says 100, but that might not take us from
+ * normal to bold.
+ */
+
+ PangoWeight old_weight = pango_font_description_get_weight (node->font_desc);
+ if (weight == PANGO_WEIGHT_BOLD)
+ weight = old_weight + 200;
+ else
+ weight = old_weight - 200;
+
+ if (weight < 100)
+ weight = 100;
+ if (weight > 900)
+ weight = 900;
+ }
+
+ pango_font_description_set_weight (node->font_desc, weight);
+ }
+
+ if (font_style_set)
+ pango_font_description_set_style (node->font_desc, font_style);
+ if (variant_set)
+ pango_font_description_set_variant (node->font_desc, variant);
+
+ return node->font_desc;
+}
+
+/**
+ * st_theme_node_get_font_features:
+ * @node: a #StThemeNode
+ *
+ * Get the CSS font-features for @node.
+ *
+ * Returns: (transfer full): font-features as a string
+ */
+gchar *
+st_theme_node_get_font_features (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "font-feature-settings") == 0)
+ {
+ CRTerm *term = decl->value;
+
+ if (!term->next && term->type == TERM_IDENT)
+ {
+ gchar *ident = term->content.str->stryng->str;
+
+ if (strcmp (ident, "inherit") == 0)
+ break;
+
+ if (strcmp (ident, "normal") == 0)
+ return NULL;
+ }
+
+ return (gchar *)cr_term_to_string (term);
+ }
+ }
+
+ return node->parent_node ? st_theme_node_get_font_features (node->parent_node) : NULL;
+}
+
+/**
+ * st_theme_node_get_border_image:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the border-image style property
+ *
+ * Returns: (transfer none): the border image, or %NULL
+ * if there is no border image.
+ */
+StBorderImage *
+st_theme_node_get_border_image (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->border_image_computed)
+ return node->border_image;
+
+ node->border_image = NULL;
+ node->border_image_computed = TRUE;
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "border-image") == 0)
+ {
+ CRTerm *term = decl->value;
+ CRStyleSheet *base_stylesheet;
+ int borders[4];
+ int n_borders = 0;
+ int j;
+
+ const char *url;
+ int border_top;
+ int border_right;
+ int border_bottom;
+ int border_left;
+
+ GFile *file;
+
+ /* Support border-image: none; to suppress a previously specified border image */
+ if (term_is_none (term))
+ {
+ if (term->next == NULL)
+ return NULL;
+ else
+ goto next_property;
+ }
+
+ /* First term must be the URL to the image */
+ if (term->type != TERM_URI)
+ goto next_property;
+
+ url = term->content.str->stryng->str;
+
+ term = term->next;
+
+ /* Followed by 0 to 4 numbers or percentages. *Not lengths*. The interpretation
+ * of a number is supposed to be pixels if the image is pixel based, otherwise CSS pixels.
+ */
+ for (j = 0; j < 4; j++)
+ {
+ if (term == NULL)
+ break;
+
+ if (term->type != TERM_NUMBER)
+ goto next_property;
+
+ if (term->content.num->type == NUM_GENERIC)
+ {
+ borders[n_borders] = (int)(0.5 + term->content.num->val);
+ n_borders++;
+ }
+ else if (term->content.num->type == NUM_PERCENTAGE)
+ {
+ /* This would be easiest to support if we moved image handling into StBorderImage */
+ g_warning ("Percentages not supported for border-image");
+ goto next_property;
+ }
+ else
+ goto next_property;
+
+ term = term->next;
+ }
+
+ switch (n_borders)
+ {
+ case 0:
+ border_top = border_right = border_bottom = border_left = 0;
+ break;
+ case 1:
+ border_top = border_right = border_bottom = border_left = borders[0];
+ break;
+ case 2:
+ border_top = border_bottom = borders[0];
+ border_left = border_right = borders[1];
+ break;
+ case 3:
+ border_top = borders[0];
+ border_left = border_right = borders[1];
+ border_bottom = borders[2];
+ break;
+ case 4:
+ default:
+ border_top = borders[0];
+ border_right = borders[1];
+ border_bottom = borders[2];
+ border_left = borders[3];
+ break;
+ }
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ file = _st_theme_resolve_url (node->theme, base_stylesheet, url);
+
+ if (file == NULL)
+ goto next_property;
+
+ node->border_image = st_border_image_new (file,
+ border_top, border_right, border_bottom, border_left,
+ node->cached_scale_factor);
+
+ g_object_unref (file);
+
+ return node->border_image;
+ }
+
+ next_property:
+ ;
+ }
+
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_horizontal_padding:
+ * @node: a #StThemeNode
+ *
+ * Gets the total horizontal padding (left + right padding), in physical pixels.
+ *
+ * Returns: the total horizontal padding in physical pixels
+ */
+double
+st_theme_node_get_horizontal_padding (StThemeNode *node)
+{
+ double padding = 0.0;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), padding);
+
+ padding += st_theme_node_get_padding (node, ST_SIDE_LEFT);
+ padding += st_theme_node_get_padding (node, ST_SIDE_RIGHT);
+
+ return padding;
+}
+
+/**
+ * st_theme_node_get_vertical_padding:
+ * @node: a #StThemeNode
+ *
+ * Gets the total vertical padding (top + bottom padding), in physical pixels.
+ *
+ * Returns: the total vertical padding in physical pixels
+ */
+double
+st_theme_node_get_vertical_padding (StThemeNode *node)
+{
+ double padding = 0.0;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), padding);
+
+ padding += st_theme_node_get_padding (node, ST_SIDE_TOP);
+ padding += st_theme_node_get_padding (node, ST_SIDE_BOTTOM);
+
+ return padding;
+}
+
+void
+_st_theme_node_apply_margins (StThemeNode *node,
+ ClutterActor *actor)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ clutter_actor_set_margin_left (actor, st_theme_node_get_margin(node, ST_SIDE_LEFT));
+ clutter_actor_set_margin_right (actor, st_theme_node_get_margin(node, ST_SIDE_RIGHT));
+ clutter_actor_set_margin_top (actor, st_theme_node_get_margin(node, ST_SIDE_TOP));
+ clutter_actor_set_margin_bottom (actor, st_theme_node_get_margin(node, ST_SIDE_BOTTOM));
+}
+
+static GetFromTermResult
+parse_shadow_property (StThemeNode *node,
+ CRDeclaration *decl,
+ ClutterColor *color,
+ gdouble *xoffset,
+ gdouble *yoffset,
+ gdouble *blur,
+ gdouble *spread,
+ gboolean *inset,
+ gboolean *is_none)
+{
+ GetFromTermResult result;
+ CRTerm *term;
+ int n_offsets = 0;
+ *is_none = FALSE;
+
+ /* default values */
+ color->red = 0x0; color->green = 0x0; color->blue = 0x0; color->alpha = 0xff;
+ *xoffset = 0.;
+ *yoffset = 0.;
+ *blur = 0.;
+ *spread = 0.;
+ *inset = FALSE;
+
+ /* The CSS3 draft of the box-shadow property[0] is a lot stricter
+ * regarding the order of terms:
+ * If the 'inset' keyword is specified, it has to be first or last,
+ * and the color may not be mixed with the lengths; while we parse
+ * length values in the correct order, we allow for arbitrary
+ * placement of the color and 'inset' keyword.
+ *
+ * [0] http://www.w3.org/TR/css3-background/#box-shadow
+ */
+ for (term = decl->value; term; term = term->next)
+ {
+ /* if we found "none", we're all set with the default values */
+ if (term_is_none (term)) {
+ *is_none = TRUE;
+ return VALUE_FOUND;
+ }
+
+ if (term->type == TERM_NUMBER)
+ {
+ gdouble value;
+ gdouble multiplier;
+
+ multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.;
+ result = get_length_from_term (node, term, FALSE, &value);
+
+ if (result == VALUE_INHERIT)
+ {
+ /* we only allow inherit on the line by itself */
+ if (n_offsets > 0)
+ return VALUE_NOT_FOUND;
+ else
+ return VALUE_INHERIT;
+ }
+ else if (result == VALUE_FOUND)
+ {
+ switch (n_offsets++)
+ {
+ case 0:
+ *xoffset = multiplier * value;
+ break;
+ case 1:
+ *yoffset = multiplier * value;
+ break;
+ case 2:
+ if (multiplier < 0)
+ g_warning ("Negative blur values are "
+ "not allowed");
+ *blur = value;
+ break;
+ case 3:
+ if (multiplier < 0)
+ g_warning ("Negative spread values are "
+ "not allowed");
+ *spread = value;
+ break;
+ default:
+ g_warning ("Ignoring excess values in shadow definition");
+ break;
+ }
+ continue;
+ }
+ }
+ else if (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "inset") == 0)
+ {
+ *inset = TRUE;
+ continue;
+ }
+
+ result = get_color_from_term (node, term, color);
+
+ if (result == VALUE_INHERIT)
+ {
+ if (n_offsets > 0)
+ return VALUE_NOT_FOUND;
+ else
+ return VALUE_INHERIT;
+ }
+ else if (result == VALUE_FOUND)
+ {
+ continue;
+ }
+ }
+
+ /* The only required terms are the x and y offsets
+ */
+ if (n_offsets >= 2)
+ return VALUE_FOUND;
+ else
+ return VALUE_NOT_FOUND;
+}
+
+/**
+ * st_theme_node_lookup_shadow:
+ * @node: a #StThemeNode
+ * @property_name: The name of the shadow property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @shadow: (out): location to store the shadow
+ *
+ * If the property is not found, the value in the shadow variable will not
+ * be changed.
+ *
+ * Generically looks up a property containing a set of shadow values. When
+ * specific getters (like st_theme_node_get_box_shadow ()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * See also st_theme_node_get_shadow(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.), %FALSE
+ * if the property was not found, or was explicitly set to 'none'.
+ */
+gboolean
+st_theme_node_lookup_shadow (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ StShadow **shadow)
+{
+ ClutterColor color = { 0., };
+ gdouble xoffset = 0.;
+ gdouble yoffset = 0.;
+ gdouble blur = 0.;
+ gdouble spread = 0.;
+ gboolean inset = FALSE;
+ gboolean is_none = FALSE;
+
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ GetFromTermResult result = parse_shadow_property (node,
+ decl,
+ &color,
+ &xoffset,
+ &yoffset,
+ &blur,
+ &spread,
+ &inset,
+ &is_none);
+ if (result == VALUE_FOUND)
+ {
+ if (is_none)
+ return FALSE;
+
+ *shadow = st_shadow_new (&color,
+ xoffset, yoffset,
+ blur, spread,
+ inset);
+ return TRUE;
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ return st_theme_node_lookup_shadow (node->parent_node,
+ property_name,
+ inherit,
+ shadow);
+ else
+ break;
+ }
+ }
+ }
+
+ if (inherit && node->parent_node)
+ return st_theme_node_lookup_shadow (node->parent_node,
+ property_name,
+ inherit,
+ shadow);
+
+ return FALSE;
+}
+
+/**
+ * st_theme_node_get_shadow:
+ * @node: a #StThemeNode
+ * @property_name: The name of the shadow property
+ *
+ * Generically looks up a property containing a set of shadow values. When
+ * specific getters (like st_theme_node_get_box_shadow()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * Like st_theme_get_length(), this does not print a warning if the property is
+ * not found; it just returns %NULL
+ *
+ * See also st_theme_node_lookup_shadow (), which provides more options.
+ *
+ * Returns: (nullable) (transfer full): the shadow, or %NULL if the property was
+ * not found.
+ */
+StShadow *
+st_theme_node_get_shadow (StThemeNode *node,
+ const char *property_name)
+{
+ StShadow *shadow;
+
+ if (st_theme_node_lookup_shadow (node, property_name, FALSE, &shadow))
+ return shadow;
+ else
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_box_shadow:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the box-shadow style property
+ *
+ * Returns: (nullable) (transfer none): the node's shadow, or %NULL
+ * if node has no shadow
+ */
+StShadow *
+st_theme_node_get_box_shadow (StThemeNode *node)
+{
+ StShadow *shadow;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->box_shadow_computed)
+ return node->box_shadow;
+
+ node->box_shadow = NULL;
+ node->box_shadow_computed = TRUE;
+
+ if (st_theme_node_lookup_shadow (node,
+ "box-shadow",
+ FALSE,
+ &shadow))
+ {
+ node->box_shadow = shadow;
+
+ return node->box_shadow;
+ }
+
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_background_image_shadow:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the -st-background-image-shadow style property
+ *
+ * Returns: (nullable) (transfer none): the node's background image shadow, or
+ * %NULL if node has no such shadow
+ */
+StShadow *
+st_theme_node_get_background_image_shadow (StThemeNode *node)
+{
+ StShadow *shadow;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->background_image_shadow_computed)
+ return node->background_image_shadow;
+
+ node->background_image_shadow = NULL;
+ node->background_image_shadow_computed = TRUE;
+
+ if (st_theme_node_lookup_shadow (node,
+ "-st-background-image-shadow",
+ FALSE,
+ &shadow))
+ {
+ if (shadow->inset)
+ {
+ g_warning ("The -st-background-image-shadow property does not "
+ "support inset shadows");
+ st_shadow_unref (shadow);
+ shadow = NULL;
+ }
+
+ node->background_image_shadow = shadow;
+
+ return node->background_image_shadow;
+ }
+
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_text_shadow:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the text-shadow style property
+ *
+ * Returns: (nullable) (transfer none): the node's text-shadow, or %NULL
+ * if node has no text-shadow
+ */
+StShadow *
+st_theme_node_get_text_shadow (StThemeNode *node)
+{
+ StShadow *result = NULL;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->text_shadow_computed)
+ return node->text_shadow;
+
+ ensure_properties (node);
+
+ if (!st_theme_node_lookup_shadow (node,
+ "text-shadow",
+ FALSE,
+ &result))
+ {
+ if (node->parent_node)
+ {
+ result = st_theme_node_get_text_shadow (node->parent_node);
+ if (result)
+ st_shadow_ref (result);
+ }
+ }
+
+ if (result && result->inset)
+ {
+ g_warning ("The text-shadow property does not support inset shadows");
+ st_shadow_unref (result);
+ result = NULL;
+ }
+
+ node->text_shadow = result;
+ node->text_shadow_computed = TRUE;
+
+ return result;
+}
+
+/**
+ * st_theme_node_get_icon_colors:
+ * @node: a #StThemeNode
+ *
+ * Gets the colors that should be used for colorizing symbolic icons according
+ * the style of this node.
+ *
+ * Returns: (transfer none): the icon colors to use for this theme node
+ */
+StIconColors *
+st_theme_node_get_icon_colors (StThemeNode *node)
+{
+ /* Foreground here will always be the same as st_theme_node_get_foreground_color(),
+ * but there's a loss of symmetry and little efficiency win if we try to exploit
+ * that. */
+
+ enum {
+ FOREGROUND = 1 << 0,
+ WARNING = 1 << 1,
+ ERROR = 1 << 2,
+ SUCCESS = 1 << 3
+ };
+
+ gboolean shared_with_parent;
+ int i;
+ ClutterColor color = { 0, };
+
+ guint still_need = FOREGROUND | WARNING | ERROR | SUCCESS;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->icon_colors)
+ return node->icon_colors;
+
+ if (node->parent_node)
+ {
+ node->icon_colors = st_theme_node_get_icon_colors (node->parent_node);
+ shared_with_parent = TRUE;
+ }
+ else
+ {
+ node->icon_colors = st_icon_colors_new ();
+ node->icon_colors->foreground = BLACK_COLOR;
+ node->icon_colors->warning = DEFAULT_WARNING_COLOR;
+ node->icon_colors->error = DEFAULT_ERROR_COLOR;
+ node->icon_colors->success = DEFAULT_SUCCESS_COLOR;
+ shared_with_parent = FALSE;
+ }
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0 && still_need != 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+ GetFromTermResult result = VALUE_NOT_FOUND;
+ guint found = 0;
+
+ if ((still_need & FOREGROUND) != 0 &&
+ strcmp (decl->property->stryng->str, "color") == 0)
+ {
+ found = FOREGROUND;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+ else if ((still_need & WARNING) != 0 &&
+ strcmp (decl->property->stryng->str, "warning-color") == 0)
+ {
+ found = WARNING;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+ else if ((still_need & ERROR) != 0 &&
+ strcmp (decl->property->stryng->str, "error-color") == 0)
+ {
+ found = ERROR;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+ else if ((still_need & SUCCESS) != 0 &&
+ strcmp (decl->property->stryng->str, "success-color") == 0)
+ {
+ found = SUCCESS;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+
+ if (result == VALUE_INHERIT)
+ {
+ still_need &= ~found;
+ }
+ else if (result == VALUE_FOUND)
+ {
+ still_need &= ~found;
+ if (shared_with_parent)
+ {
+ node->icon_colors = st_icon_colors_copy (node->icon_colors);
+ shared_with_parent = FALSE;
+ }
+
+ switch (found)
+ {
+ case FOREGROUND:
+ node->icon_colors->foreground = color;
+ break;
+ case WARNING:
+ node->icon_colors->warning = color;
+ break;
+ case ERROR:
+ node->icon_colors->error = color;
+ break;
+ case SUCCESS:
+ node->icon_colors->success = color;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ }
+ }
+
+ if (shared_with_parent)
+ st_icon_colors_ref (node->icon_colors);
+
+ return node->icon_colors;
+}
+
+static float
+get_width_inc (StThemeNode *node)
+{
+ return ((int)(0.5 + node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT] +
+ (int)(0.5 + node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
+}
+
+static float
+get_height_inc (StThemeNode *node)
+{
+ return ((int)(0.5 + node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP] +
+ (int)(0.5 + node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
+}
+
+/**
+ * st_theme_node_adjust_for_height:
+ * @node: a #StThemeNode
+ * @for_height: (inout): the "for height" to adjust
+ *
+ * Adjusts a "for height" passed to clutter_actor_get_preferred_width() to
+ * account for borders and padding. This is a convenience function meant
+ * to be called from a get_preferred_width() method of a #ClutterActor
+ * subclass. The value after adjustment is the height available for the actor's
+ * content.
+ */
+void
+st_theme_node_adjust_for_height (StThemeNode *node,
+ float *for_height)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (for_height != NULL);
+
+ if (*for_height >= 0)
+ {
+ float height_inc = get_height_inc (node);
+ *for_height = MAX (0, *for_height - height_inc);
+ }
+}
+
+/**
+ * st_theme_node_adjust_preferred_width:
+ * @node: a #StThemeNode
+ * @min_width_p: (inout) (nullable): the minimum width to adjust
+ * @natural_width_p: (inout): the natural width to adjust
+ *
+ * Adjusts the minimum and natural width computed for an actor by
+ * adding on the necessary space for borders and padding and taking
+ * into account any minimum or maximum width. This is a convenience
+ * function meant to be called from the get_preferred_width() method
+ * of a #ClutterActor subclass
+ */
+void
+st_theme_node_adjust_preferred_width (StThemeNode *node,
+ float *min_width_p,
+ float *natural_width_p)
+{
+ float width_inc;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ width_inc = get_width_inc (node);
+
+ if (min_width_p)
+ {
+ if (node->min_width != -1)
+ *min_width_p = node->min_width;
+ *min_width_p += width_inc;
+ }
+
+ if (natural_width_p)
+ {
+ if (node->width != -1)
+ *natural_width_p = MAX (*natural_width_p, node->width);
+ if (node->max_width != -1)
+ *natural_width_p = MIN (*natural_width_p, node->max_width);
+ *natural_width_p += width_inc;
+ }
+}
+
+/**
+ * st_theme_node_adjust_for_width:
+ * @node: a #StThemeNode
+ * @for_width: (inout): the "for width" to adjust
+ *
+ * Adjusts a "for width" passed to clutter_actor_get_preferred_height() to
+ * account for borders and padding. This is a convenience function meant
+ * to be called from a get_preferred_height() method of a #ClutterActor
+ * subclass. The value after adjustment is the width available for the actor's
+ * content.
+ */
+void
+st_theme_node_adjust_for_width (StThemeNode *node,
+ float *for_width)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (for_width != NULL);
+
+ if (*for_width >= 0)
+ {
+ float width_inc = get_width_inc (node);
+ *for_width = MAX (0, *for_width - width_inc);
+ }
+}
+
+/**
+ * st_theme_node_adjust_preferred_height:
+ * @node: a #StThemeNode
+ * @min_height_p: (inout) (nullable): the minimum height to adjust
+ * @natural_height_p: (inout): the natural height to adjust
+ *
+ * Adjusts the minimum and natural height computed for an actor by
+ * adding on the necessary space for borders and padding and taking
+ * into account any minimum or maximum height. This is a convenience
+ * function meant to be called from the get_preferred_height() method
+ * of a #ClutterActor subclass
+ */
+void
+st_theme_node_adjust_preferred_height (StThemeNode *node,
+ float *min_height_p,
+ float *natural_height_p)
+{
+ float height_inc;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ height_inc = get_height_inc (node);
+
+ if (min_height_p)
+ {
+ if (node->min_height != -1)
+ *min_height_p = node->min_height;
+ *min_height_p += height_inc;
+ }
+ if (natural_height_p)
+ {
+ if (node->height != -1)
+ *natural_height_p = MAX (*natural_height_p, node->height);
+ if (node->max_height != -1)
+ *natural_height_p = MIN (*natural_height_p, node->max_height);
+ *natural_height_p += height_inc;
+ }
+}
+
+/**
+ * st_theme_node_get_content_box:
+ * @node: a #StThemeNode
+ * @allocation: the box allocated to a #ClutterAlctor
+ * @content_box: (out caller-allocates): computed box occupied by the actor's content
+ *
+ * Gets the box within an actor's allocation that contents the content
+ * of an actor (excluding borders and padding). This is a convenience function
+ * meant to be used from the allocate() or paint() methods of a #ClutterActor
+ * subclass.
+ */
+void
+st_theme_node_get_content_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *content_box)
+{
+ double noncontent_left, noncontent_top, noncontent_right, noncontent_bottom;
+ double avail_width, avail_height, content_width, content_height;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ avail_width = allocation->x2 - allocation->x1;
+ avail_height = allocation->y2 - allocation->y1;
+
+ noncontent_left = node->border_width[ST_SIDE_LEFT] + node->padding[ST_SIDE_LEFT];
+ noncontent_top = node->border_width[ST_SIDE_TOP] + node->padding[ST_SIDE_TOP];
+ noncontent_right = node->border_width[ST_SIDE_RIGHT] + node->padding[ST_SIDE_RIGHT];
+ noncontent_bottom = node->border_width[ST_SIDE_BOTTOM] + node->padding[ST_SIDE_BOTTOM];
+
+ content_box->x1 = (int)(0.5 + noncontent_left);
+ content_box->y1 = (int)(0.5 + noncontent_top);
+
+ content_width = avail_width - noncontent_left - noncontent_right;
+ if (content_width < 0)
+ content_width = 0;
+ content_height = avail_height - noncontent_top - noncontent_bottom;
+ if (content_height < 0)
+ content_height = 0;
+
+ content_box->x2 = (int)(0.5 + content_box->x1 + content_width);
+ content_box->y2 = (int)(0.5 + content_box->y1 + content_height);
+}
+
+/**
+ * st_theme_node_get_background_paint_box:
+ * @node: a #StThemeNode
+ * @allocation: the box allocated to a #ClutterActor
+ * @paint_box: (out caller-allocates): computed box occupied when painting the actor's background
+ *
+ * Gets the box used to paint the actor's background, including the area
+ * occupied by properties which paint outside the actor's assigned allocation.
+ */
+void
+st_theme_node_get_background_paint_box (StThemeNode *node,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *paint_box)
+{
+ StShadow *background_image_shadow;
+ ClutterActorBox shadow_box;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (actor_box != NULL);
+ g_return_if_fail (paint_box != NULL);
+
+ background_image_shadow = st_theme_node_get_background_image_shadow (node);
+
+ *paint_box = *actor_box;
+
+ if (!background_image_shadow)
+ return;
+
+ st_shadow_get_box (background_image_shadow, actor_box, &shadow_box);
+
+ paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
+ paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
+ paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
+ paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
+}
+
+/**
+ * st_theme_node_get_paint_box:
+ * @node: a #StThemeNode
+ * @allocation: the box allocated to a #ClutterActor
+ * @paint_box: (out caller-allocates): computed box occupied when painting the actor
+ *
+ * Gets the box used to paint the actor, including the area occupied
+ * by properties which paint outside the actor's assigned allocation.
+ * When painting @node to an offscreen buffer, this function can be
+ * used to determine the necessary size of the buffer.
+ */
+void
+st_theme_node_get_paint_box (StThemeNode *node,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *paint_box)
+{
+ StShadow *box_shadow;
+ ClutterActorBox shadow_box;
+ int outline_width;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (actor_box != NULL);
+ g_return_if_fail (paint_box != NULL);
+
+ box_shadow = st_theme_node_get_box_shadow (node);
+ outline_width = st_theme_node_get_outline_width (node);
+
+ st_theme_node_get_background_paint_box (node, actor_box, paint_box);
+
+ if (!box_shadow && !outline_width)
+ return;
+
+ paint_box->x1 -= outline_width;
+ paint_box->x2 += outline_width;
+ paint_box->y1 -= outline_width;
+ paint_box->y2 += outline_width;
+
+ if (box_shadow)
+ {
+ st_shadow_get_box (box_shadow, actor_box, &shadow_box);
+
+ paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
+ paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
+ paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
+ paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
+ }
+}
+
+/**
+ * st_theme_node_geometry_equal:
+ * @node: a #StThemeNode
+ * @other: a different #StThemeNode
+ *
+ * Tests if two theme nodes have the same borders and padding; this can be
+ * used to optimize having to relayout when the style applied to a Clutter
+ * actor changes colors without changing the geometry.
+ *
+ * Returns: %TRUE if equal, %FALSE otherwise
+ */
+gboolean
+st_theme_node_geometry_equal (StThemeNode *node,
+ StThemeNode *other)
+{
+ StSide side;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
+
+ if (node == other)
+ return TRUE;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (other), FALSE);
+
+ if (node->cached_scale_factor != other->cached_scale_factor)
+ return FALSE;
+
+ _st_theme_node_ensure_geometry (node);
+ _st_theme_node_ensure_geometry (other);
+
+ for (side = ST_SIDE_TOP; side <= ST_SIDE_LEFT; side++)
+ {
+ if (node->border_width[side] != other->border_width[side])
+ return FALSE;
+ if (node->padding[side] != other->padding[side])
+ return FALSE;
+ }
+
+ if (node->width != other->width || node->height != other->height)
+ return FALSE;
+ if (node->min_width != other->min_width || node->min_height != other->min_height)
+ return FALSE;
+ if (node->max_width != other->max_width || node->max_height != other->max_height)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_paint_equal:
+ * @node: (nullable): a #StThemeNode
+ * @other: (nullable): a different #StThemeNode
+ *
+ * Check if st_theme_node_paint() will paint identically for @node as it does
+ * for @other. Note that in some cases this function may return %TRUE even
+ * if there is no visible difference in the painting.
+ *
+ * Returns: %TRUE if the two theme nodes paint identically. %FALSE if the
+ * two nodes potentially paint differently.
+ */
+gboolean
+st_theme_node_paint_equal (StThemeNode *node,
+ StThemeNode *other)
+{
+ StBorderImage *border_image, *other_border_image;
+ StShadow *shadow, *other_shadow;
+ int i;
+
+ /* Make sure NULL != NULL */
+ if (node == NULL || other == NULL)
+ return FALSE;
+
+ if (node == other)
+ return TRUE;
+
+ _st_theme_node_ensure_background (node);
+ _st_theme_node_ensure_background (other);
+
+ if (!clutter_color_equal (&node->background_color, &other->background_color))
+ return FALSE;
+
+ if (node->background_gradient_type != other->background_gradient_type)
+ return FALSE;
+
+ if (node->background_gradient_type != ST_GRADIENT_NONE &&
+ !clutter_color_equal (&node->background_gradient_end, &other->background_gradient_end))
+ return FALSE;
+
+ if ((node->background_image != NULL) &&
+ (other->background_image != NULL) &&
+ !g_file_equal (node->background_image, other->background_image))
+ return FALSE;
+
+ _st_theme_node_ensure_geometry (node);
+ _st_theme_node_ensure_geometry (other);
+
+ for (i = 0; i < 4; i++)
+ {
+ if (node->border_width[i] != other->border_width[i])
+ return FALSE;
+
+ if (node->border_width[i] > 0 &&
+ !clutter_color_equal (&node->border_color[i], &other->border_color[i]))
+ return FALSE;
+
+ if (node->border_radius[i] != other->border_radius[i])
+ return FALSE;
+ }
+
+ if (node->outline_width != other->outline_width)
+ return FALSE;
+
+ if (node->outline_width > 0 &&
+ !clutter_color_equal (&node->outline_color, &other->outline_color))
+ return FALSE;
+
+ border_image = st_theme_node_get_border_image (node);
+ other_border_image = st_theme_node_get_border_image (other);
+
+ if ((border_image == NULL) != (other_border_image == NULL))
+ return FALSE;
+
+ if (border_image != NULL && !st_border_image_equal (border_image, other_border_image))
+ return FALSE;
+
+ shadow = st_theme_node_get_box_shadow (node);
+ other_shadow = st_theme_node_get_box_shadow (other);
+
+ if ((shadow == NULL) != (other_shadow == NULL))
+ return FALSE;
+
+ if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
+ return FALSE;
+
+ shadow = st_theme_node_get_background_image_shadow (node);
+ other_shadow = st_theme_node_get_background_image_shadow (other);
+
+ if ((shadow == NULL) != (other_shadow == NULL))
+ return FALSE;
+
+ if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_to_string:
+ * @node: a #StThemeNode
+ *
+ * Serialize @node to a string of its #GType name, CSS ID, classes and
+ * pseudo-classes.
+ *
+ * Returns: the serialized theme node
+ */
+gchar *
+st_theme_node_to_string (StThemeNode *node)
+{
+ GString *desc;
+ gchar **it;
+
+ if (!node)
+ return g_strdup ("[null]");
+
+ desc = g_string_new (NULL);
+ g_string_append_printf (desc,
+ "[%p %s#%s",
+ node,
+ g_type_name (node->element_type),
+ node->element_id);
+
+ for (it = node->element_classes; it && *it; it++)
+ g_string_append_printf (desc, ".%s", *it);
+
+ for (it = node->pseudo_classes; it && *it; it++)
+ g_string_append_printf (desc, ":%s", *it);
+
+ g_string_append_c (desc, ']');
+
+ return g_string_free (desc, FALSE);
+}
diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h
new file mode 100644
index 0000000..520e29f
--- /dev/null
+++ b/src/st/st-theme-node.h
@@ -0,0 +1,368 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node.h: style information for one node in a tree of themed objects
+ *
+ * Copyright 2008-2010 Red Hat, Inc.
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_NODE_H__
+#define __ST_THEME_NODE_H__
+
+#include <clutter/clutter.h>
+#include "st-border-image.h"
+#include "st-icon-colors.h"
+#include "st-shadow.h"
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-theme-node
+ * @short_description: style information for one node in a tree of themed objects
+ *
+ * A #StThemeNode represents the CSS style information (the set of CSS properties) for one
+ * node in a tree of themed objects. In typical usage, it represents the style information
+ * for a single #ClutterActor. A #StThemeNode is immutable: attributes such as the
+ * CSS classes for the node are passed in at construction. If the attributes of the node
+ * or any parent node change, the node should be discarded and a new node created.
+ * #StThemeNode has generic accessors to look up properties by name and specific
+ * accessors for standard CSS properties that add caching and handling of various
+ * details of the CSS specification. #StThemeNode also has convenience functions to help
+ * in implementing a #ClutterActor with borders and padding.
+ *
+ * Note that pixel measurements take the #StThemeContext:scale-factor into
+ * account so all values are in physical pixels, as opposed to logical pixels.
+ * Physical pixels correspond to actor sizes, not necessarily to pixels on
+ * display devices (eg. when `scale-monitor-framebuffer` is enabled).
+ */
+
+typedef struct _StTheme StTheme;
+typedef struct _StThemeContext StThemeContext;
+
+#define ST_TYPE_THEME_NODE (st_theme_node_get_type ())
+G_DECLARE_FINAL_TYPE (StThemeNode, st_theme_node, ST, THEME_NODE, GObject)
+
+/**
+ * StSide:
+ * @ST_SIDE_TOP: The top side.
+ * @ST_SIDE_RIGHT: The right side.
+ * @ST_SIDE_BOTTOM: The bottom side.
+ * @ST_SIDE_LEFT: The left side.
+ *
+ * Used to target a particular side of a #StThemeNode element.
+ */
+typedef enum {
+ ST_SIDE_TOP,
+ ST_SIDE_RIGHT,
+ ST_SIDE_BOTTOM,
+ ST_SIDE_LEFT
+} StSide;
+
+/**
+ * StCorner:
+ * @ST_CORNER_TOPLEFT: The top-right corner.
+ * @ST_CORNER_TOPRIGHT: The top-right corner.
+ * @ST_CORNER_BOTTOMRIGHT: The bottom-right corner.
+ * @ST_CORNER_BOTTOMLEFT: The bottom-left corner.
+ *
+ * Used to target a particular corner of a #StThemeNode element.
+ */
+typedef enum {
+ ST_CORNER_TOPLEFT,
+ ST_CORNER_TOPRIGHT,
+ ST_CORNER_BOTTOMRIGHT,
+ ST_CORNER_BOTTOMLEFT
+} StCorner;
+
+/* These are the CSS values; that doesn't mean we have to implement blink... */
+/**
+ * StTextDecoration:
+ * @ST_TEXT_DECORATION_: Text is underlined
+ * @ST_TEXT_DECORATION_OVERLINE: Text is overlined
+ * @ST_TEXT_DECORATION_LINE_THROUGH: Text is striked out
+ * @ST_TEXT_DECORATION_BLINK: Text blinks
+ *
+ * Flags used to determine the decoration of text.
+ *
+ * Not that neither %ST_TEXT_DECORATION_OVERLINE or %ST_TEXT_DECORATION_BLINK
+ * are implemented, currently.
+ */
+typedef enum {
+ ST_TEXT_DECORATION_UNDERLINE = 1 << 0,
+ ST_TEXT_DECORATION_OVERLINE = 1 << 1,
+ ST_TEXT_DECORATION_LINE_THROUGH = 1 << 2,
+ ST_TEXT_DECORATION_BLINK = 1 << 3
+} StTextDecoration;
+
+/**
+ * StTextAlign:
+ * @ST_TEXT_ALIGN_LEFT: Text is aligned at the beginning of the label.
+ * @ST_TEXT_ALIGN_CENTER: Text is aligned in the middle of the label.
+ * @ST_TEXT_ALIGN_RIGHT: Text is aligned at the end of the label.
+ * @ST_GRADIENT_JUSTIFY: Text is justified in the label.
+ *
+ * Used to align text in a label.
+ */
+typedef enum {
+ ST_TEXT_ALIGN_LEFT = PANGO_ALIGN_LEFT,
+ ST_TEXT_ALIGN_CENTER = PANGO_ALIGN_CENTER,
+ ST_TEXT_ALIGN_RIGHT = PANGO_ALIGN_RIGHT,
+ ST_TEXT_ALIGN_JUSTIFY
+} StTextAlign;
+
+/**
+ * StGradientType:
+ * @ST_GRADIENT_NONE: No gradient.
+ * @ST_GRADIENT_VERTICAL: A vertical gradient.
+ * @ST_GRADIENT_HORIZONTAL: A horizontal gradient.
+ * @ST_GRADIENT_RADIAL: Lookup the style requested in the icon name.
+ *
+ * Used to specify options when rendering gradients.
+ */
+typedef enum {
+ ST_GRADIENT_NONE,
+ ST_GRADIENT_VERTICAL,
+ ST_GRADIENT_HORIZONTAL,
+ ST_GRADIENT_RADIAL
+} StGradientType;
+
+/**
+ * StIconStyle:
+ * @ST_ICON_STYLE_REQUESTED: Lookup the style requested in the icon name.
+ * @ST_ICON_STYLE_REGULAR: Try to always load regular icons, even when symbolic
+ * icon names are given.
+ * @ST_ICON_STYLE_SYMBOLIC: Try to always load symbolic icons, even when regular
+ * icon names are given.
+ *
+ * Used to specify options when looking up icons.
+ */
+typedef enum {
+ ST_ICON_STYLE_REQUESTED,
+ ST_ICON_STYLE_REGULAR,
+ ST_ICON_STYLE_SYMBOLIC
+} StIconStyle;
+
+typedef struct _StThemeNodePaintState StThemeNodePaintState;
+
+struct _StThemeNodePaintState {
+ StThemeNode *node;
+
+ float alloc_width;
+ float alloc_height;
+
+ float box_shadow_width;
+ float box_shadow_height;
+
+ float resource_scale;
+
+ CoglPipeline *box_shadow_pipeline;
+ CoglPipeline *prerendered_texture;
+ CoglPipeline *prerendered_pipeline;
+ CoglPipeline *corner_material[4];
+};
+
+StThemeNode *st_theme_node_new (StThemeContext *context,
+ StThemeNode *parent_node, /* can be null */
+ StTheme *theme, /* can be null */
+ GType element_type,
+ const char *element_id,
+ const char *element_class,
+ const char *pseudo_class,
+ const char *inline_style);
+
+StThemeNode *st_theme_node_get_parent (StThemeNode *node);
+
+StTheme *st_theme_node_get_theme (StThemeNode *node);
+
+gboolean st_theme_node_equal (StThemeNode *node_a, StThemeNode *node_b);
+guint st_theme_node_hash (StThemeNode *node);
+
+GType st_theme_node_get_element_type (StThemeNode *node);
+const char *st_theme_node_get_element_id (StThemeNode *node);
+GStrv st_theme_node_get_element_classes (StThemeNode *node);
+GStrv st_theme_node_get_pseudo_classes (StThemeNode *node);
+
+/* Generic getters ... these are not cached so are less efficient. The other
+ * reason for adding the more specific version is that we can handle the
+ * details of the actual CSS rules, which can be complicated, especially
+ * for fonts
+ */
+gboolean st_theme_node_lookup_color (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ ClutterColor *color);
+gboolean st_theme_node_lookup_double (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ double *value);
+gboolean st_theme_node_lookup_length (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ gdouble *length);
+gboolean st_theme_node_lookup_time (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ gdouble *value);
+gboolean st_theme_node_lookup_shadow (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ StShadow **shadow);
+gboolean st_theme_node_lookup_url (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ GFile **file);
+
+/* Easier-to-use variants of the above, for application-level use */
+void st_theme_node_get_color (StThemeNode *node,
+ const char *property_name,
+ ClutterColor *color);
+gdouble st_theme_node_get_double (StThemeNode *node,
+ const char *property_name);
+gdouble st_theme_node_get_length (StThemeNode *node,
+ const char *property_name);
+StShadow *st_theme_node_get_shadow (StThemeNode *node,
+ const char *property_name);
+GFile *st_theme_node_get_url (StThemeNode *node,
+ const char *property_name);
+
+/* Specific getters for particular properties: cached
+ */
+void st_theme_node_get_background_color (StThemeNode *node,
+ ClutterColor *color);
+void st_theme_node_get_foreground_color (StThemeNode *node,
+ ClutterColor *color);
+void st_theme_node_get_background_gradient (StThemeNode *node,
+ StGradientType *type,
+ ClutterColor *start,
+ ClutterColor *end);
+
+GFile *st_theme_node_get_background_image (StThemeNode *node);
+
+int st_theme_node_get_border_width (StThemeNode *node,
+ StSide side);
+int st_theme_node_get_border_radius (StThemeNode *node,
+ StCorner corner);
+void st_theme_node_get_border_color (StThemeNode *node,
+ StSide side,
+ ClutterColor *color);
+
+int st_theme_node_get_outline_width (StThemeNode *node);
+void st_theme_node_get_outline_color (StThemeNode *node,
+ ClutterColor *color);
+
+double st_theme_node_get_padding (StThemeNode *node,
+ StSide side);
+
+double st_theme_node_get_horizontal_padding (StThemeNode *node);
+double st_theme_node_get_vertical_padding (StThemeNode *node);
+
+double st_theme_node_get_margin (StThemeNode *node,
+ StSide side);
+
+int st_theme_node_get_width (StThemeNode *node);
+int st_theme_node_get_height (StThemeNode *node);
+int st_theme_node_get_min_width (StThemeNode *node);
+int st_theme_node_get_min_height (StThemeNode *node);
+int st_theme_node_get_max_width (StThemeNode *node);
+int st_theme_node_get_max_height (StThemeNode *node);
+
+int st_theme_node_get_transition_duration (StThemeNode *node);
+
+StIconStyle st_theme_node_get_icon_style (StThemeNode *node);
+
+StTextDecoration st_theme_node_get_text_decoration (StThemeNode *node);
+
+StTextAlign st_theme_node_get_text_align (StThemeNode *node);
+
+double st_theme_node_get_letter_spacing (StThemeNode *node);
+
+/* Font rule processing is pretty complicated, so we just hardcode it
+ * under the standard font/font-family/font-size/etc names. This means
+ * you can't have multiple separate styled fonts for a single item,
+ * but that should be OK.
+ */
+const PangoFontDescription *st_theme_node_get_font (StThemeNode *node);
+
+gchar *st_theme_node_get_font_features (StThemeNode *node);
+
+StBorderImage *st_theme_node_get_border_image (StThemeNode *node);
+StShadow *st_theme_node_get_box_shadow (StThemeNode *node);
+StShadow *st_theme_node_get_text_shadow (StThemeNode *node);
+
+StShadow *st_theme_node_get_background_image_shadow (StThemeNode *node);
+
+StIconColors *st_theme_node_get_icon_colors (StThemeNode *node);
+
+/* Helpers for get_preferred_width()/get_preferred_height() ClutterActor vfuncs */
+void st_theme_node_adjust_for_height (StThemeNode *node,
+ float *for_height);
+void st_theme_node_adjust_preferred_width (StThemeNode *node,
+ float *min_width_p,
+ float *natural_width_p);
+void st_theme_node_adjust_for_width (StThemeNode *node,
+ float *for_width);
+void st_theme_node_adjust_preferred_height (StThemeNode *node,
+ float *min_height_p,
+ float *natural_height_p);
+
+/* Helper for allocate() ClutterActor vfunc */
+void st_theme_node_get_content_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *content_box);
+/* Helper for StThemeNodeTransition */
+void st_theme_node_get_paint_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box);
+/* Helper for background prerendering */
+void st_theme_node_get_background_paint_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box);
+
+gboolean st_theme_node_geometry_equal (StThemeNode *node,
+ StThemeNode *other);
+gboolean st_theme_node_paint_equal (StThemeNode *node,
+ StThemeNode *other);
+
+/**
+ * st_theme_node_paint: (skip)
+ */
+void st_theme_node_paint (StThemeNode *node,
+ StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity,
+ float resource_scale);
+
+void st_theme_node_invalidate_background_image (StThemeNode *node);
+void st_theme_node_invalidate_border_image (StThemeNode *node);
+
+gchar * st_theme_node_to_string (StThemeNode *node);
+
+void st_theme_node_paint_state_init (StThemeNodePaintState *state);
+void st_theme_node_paint_state_free (StThemeNodePaintState *state);
+void st_theme_node_paint_state_copy (StThemeNodePaintState *state,
+ StThemeNodePaintState *other);
+void st_theme_node_paint_state_invalidate (StThemeNodePaintState *state);
+gboolean st_theme_node_paint_state_invalidate_for_file (StThemeNodePaintState *state,
+ GFile *file);
+
+void st_theme_node_paint_state_set_node (StThemeNodePaintState *state,
+ StThemeNode *node);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_NODE_H__ */
diff --git a/src/st/st-theme-private.h b/src/st/st-theme-private.h
new file mode 100644
index 0000000..2083a7c
--- /dev/null
+++ b/src/st/st-theme-private.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-private.h: Private StThemeMethods
+ *
+ * Copyright 2008, 2009 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_PRIVATE_H__
+#define __ST_THEME_PRIVATE_H__
+
+#include "croco/libcroco.h"
+#include "st-theme.h"
+
+G_BEGIN_DECLS
+
+GPtrArray *_st_theme_get_matched_properties (StTheme *theme,
+ StThemeNode *node);
+
+/* Resolve an URL from the stylesheet to a file */
+GFile *_st_theme_resolve_url (StTheme *theme,
+ CRStyleSheet *base_stylesheet,
+ const char *url);
+
+CRDeclaration *_st_theme_parse_declaration_list (const char *str);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_PRIVATE_H__ */
diff --git a/src/st/st-theme.c b/src/st/st-theme.c
new file mode 100644
index 0000000..20d42fd
--- /dev/null
+++ b/src/st/st-theme.c
@@ -0,0 +1,1085 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme.c: A set of CSS stylesheets used for rule matching
+ *
+ * Copyright 2003-2004 Dodji Seketeli
+ * Copyright 2008, 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This file started as a cut-and-paste of cr-sel-eng.c from libcroco.
+ *
+ * In moving it to hippo-canvas:
+ * - Reformatted and otherwise edited to match our coding style
+ * - Switched from handling xmlNode to handling HippoStyle
+ * - Simplified by removing things that we don't need or that don't
+ * make sense in our context.
+ * - The code to get a list of matching properties works quite differently;
+ * we order things in priority order, but we don't actually try to
+ * coalesce properties with the same name.
+ *
+ * In moving it to GNOME Shell:
+ * - Renamed again to StTheme
+ * - Reformatted to match the gnome-shell coding style
+ * - Removed notion of "theme engine" from hippo-canvas
+ * - pseudo-class matching changed from link enum to strings
+ * - Some code simplification
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gio/gio.h>
+
+#include "st-private.h"
+#include "st-theme-node.h"
+#include "st-theme-private.h"
+
+static void st_theme_constructed (GObject *object);
+static void st_theme_finalize (GObject *object);
+static void st_theme_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void st_theme_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+struct _StTheme
+{
+ GObject parent;
+
+ GFile *application_stylesheet;
+ GFile *default_stylesheet;
+ GFile *theme_stylesheet;
+ GSList *custom_stylesheets;
+
+ GHashTable *stylesheets_by_file;
+ GHashTable *files_by_stylesheet;
+
+ CRCascade *cascade;
+};
+
+enum
+{
+ PROP_0,
+ PROP_APPLICATION_STYLESHEET,
+ PROP_THEME_STYLESHEET,
+ PROP_DEFAULT_STYLESHEET
+};
+
+enum
+{
+ STYLESHEETS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT)
+
+/* Quick strcmp. Test only for == 0 or != 0, not < 0 or > 0. */
+#define strqcmp(str,lit,lit_len) \
+ (strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
+
+static gboolean
+file_equal0 (GFile *file1,
+ GFile *file2)
+{
+ if (file1 == file2)
+ return TRUE;
+
+ if ((file1 == NULL) || (file2 == NULL))
+ return FALSE;
+
+ return g_file_equal (file1, file2);
+}
+
+static void
+st_theme_init (StTheme *theme)
+{
+ theme->stylesheets_by_file = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ (GDestroyNotify)g_object_unref, (GDestroyNotify)cr_stylesheet_unref);
+ theme->files_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+st_theme_class_init (StThemeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = st_theme_constructed;
+ object_class->finalize = st_theme_finalize;
+ object_class->set_property = st_theme_set_property;
+ object_class->get_property = st_theme_get_property;
+
+ /**
+ * StTheme:application-stylesheet:
+ *
+ * The highest priority stylesheet, representing application-specific
+ * styling; this is associated with the CSS "author" stylesheet.
+ */
+ g_object_class_install_property (object_class,
+ PROP_APPLICATION_STYLESHEET,
+ g_param_spec_object ("application-stylesheet",
+ "Application Stylesheet",
+ "Stylesheet with application-specific styling",
+ G_TYPE_FILE,
+ ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * StTheme:theme-stylesheet:
+ *
+ * The second priority stylesheet, representing theme-specific styling;
+ * this is associated with the CSS "user" stylesheet.
+ */
+ g_object_class_install_property (object_class,
+ PROP_THEME_STYLESHEET,
+ g_param_spec_object ("theme-stylesheet",
+ "Theme Stylesheet",
+ "Stylesheet with theme-specific styling",
+ G_TYPE_FILE,
+ ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * StTheme:default-stylesheet:
+ *
+ * The lowest priority stylesheet, representing global default
+ * styling; this is associated with the CSS "user agent" stylesheet.
+ */
+ g_object_class_install_property (object_class,
+ PROP_DEFAULT_STYLESHEET,
+ g_param_spec_object ("default-stylesheet",
+ "Default Stylesheet",
+ "Stylesheet with global default styling",
+ G_TYPE_FILE,
+ ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ signals[STYLESHEETS_CHANGED] =
+ g_signal_new ("custom-stylesheets-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static CRStyleSheet *
+parse_stylesheet (GFile *file,
+ GError **error)
+{
+ enum CRStatus status;
+ CRStyleSheet *stylesheet;
+ char *contents;
+ gsize length;
+
+ if (file == NULL)
+ return NULL;
+
+ if (!g_file_load_contents (file, NULL, &contents, &length, NULL, error))
+ return NULL;
+
+ status = cr_om_parser_simply_parse_buf ((const guchar *) contents,
+ length,
+ CR_UTF_8,
+ &stylesheet);
+ g_free (contents);
+
+ if (status != CR_OK)
+ {
+ char *uri = g_file_get_uri (file);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error parsing stylesheet '%s'; errcode:%d", uri, status);
+ g_free (uri);
+ return NULL;
+ }
+
+ /* Extension stylesheet */
+ stylesheet->app_data = GUINT_TO_POINTER (FALSE);
+
+ return stylesheet;
+}
+
+CRDeclaration *
+_st_theme_parse_declaration_list (const char *str)
+{
+ return cr_declaration_parse_list_from_buf ((const guchar *)str,
+ CR_UTF_8);
+}
+
+/* Just g_warning for now until we have something nicer to do */
+static CRStyleSheet *
+parse_stylesheet_nofail (GFile *file)
+{
+ GError *error = NULL;
+ CRStyleSheet *result;
+
+ result = parse_stylesheet (file, &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+ return result;
+}
+
+static void
+insert_stylesheet (StTheme *theme,
+ GFile *file,
+ CRStyleSheet *stylesheet)
+{
+ if (stylesheet == NULL)
+ return;
+
+ g_object_ref (file);
+ cr_stylesheet_ref (stylesheet);
+
+ g_hash_table_insert (theme->stylesheets_by_file, file, stylesheet);
+ g_hash_table_insert (theme->files_by_stylesheet, stylesheet, file);
+}
+
+/**
+ * st_theme_load_stylesheet:
+ * @theme: a #StTheme
+ * @file: a #GFile
+ * @error: a #GError
+ *
+ * Load the stylesheet associated with @file.
+ *
+ * Returns: %TRUE if successful
+ */
+gboolean
+st_theme_load_stylesheet (StTheme *theme,
+ GFile *file,
+ GError **error)
+{
+ CRStyleSheet *stylesheet;
+
+ stylesheet = parse_stylesheet (file, error);
+ if (!stylesheet)
+ return FALSE;
+
+ stylesheet->app_data = GUINT_TO_POINTER (TRUE);
+
+ insert_stylesheet (theme, file, stylesheet);
+ cr_stylesheet_ref (stylesheet);
+ theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);
+ g_signal_emit (theme, signals[STYLESHEETS_CHANGED], 0);
+
+ return TRUE;
+}
+
+/**
+ * st_theme_unload_stylesheet:
+ * @theme: a #StTheme
+ * @file: a #GFile
+ *
+ * Unload the stylesheet associated with @file. If @file was not loaded this
+ * function does nothing.
+ */
+void
+st_theme_unload_stylesheet (StTheme *theme,
+ GFile *file)
+{
+ CRStyleSheet *stylesheet;
+
+ stylesheet = g_hash_table_lookup (theme->stylesheets_by_file, file);
+ if (!stylesheet)
+ return;
+
+ if (!g_slist_find (theme->custom_stylesheets, stylesheet))
+ return;
+
+ theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet);
+
+ g_signal_emit (theme, signals[STYLESHEETS_CHANGED], 0);
+
+ /* We need to remove the entry from the hashtable after emitting the signal
+ * since we might still access the files_by_stylesheet hashtable in
+ * _st_theme_resolve_url() during the signal emission.
+ */
+ g_hash_table_remove (theme->stylesheets_by_file, file);
+ g_hash_table_remove (theme->files_by_stylesheet, stylesheet);
+ cr_stylesheet_unref (stylesheet);
+}
+
+/**
+ * st_theme_get_custom_stylesheets:
+ * @theme: an #StTheme
+ *
+ * Get a list of the stylesheet files loaded with st_theme_load_stylesheet().
+ *
+ * Returns: (transfer full) (element-type GFile): the list of stylesheet files
+ * that were loaded with st_theme_load_stylesheet()
+ */
+GSList*
+st_theme_get_custom_stylesheets (StTheme *theme)
+{
+ GSList *result = NULL;
+ GSList *iter;
+
+ for (iter = theme->custom_stylesheets; iter; iter = iter->next)
+ {
+ CRStyleSheet *stylesheet = iter->data;
+ GFile *file = g_hash_table_lookup (theme->files_by_stylesheet, stylesheet);
+
+ result = g_slist_prepend (result, g_object_ref (file));
+ }
+
+ return result;
+}
+
+static void
+st_theme_constructed (GObject *object)
+{
+ StTheme *theme = ST_THEME (object);
+ CRStyleSheet *application_stylesheet;
+ CRStyleSheet *theme_stylesheet;
+ CRStyleSheet *default_stylesheet;
+
+ G_OBJECT_CLASS (st_theme_parent_class)->constructed (object);
+
+ application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
+ theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
+ default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
+
+ theme->cascade = cr_cascade_new (application_stylesheet,
+ theme_stylesheet,
+ default_stylesheet);
+
+ if (theme->cascade == NULL)
+ g_error ("Out of memory when creating cascade object");
+
+ insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet);
+ insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet);
+ insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet);
+}
+
+static void
+st_theme_finalize (GObject * object)
+{
+ StTheme *theme = ST_THEME (object);
+
+ g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL);
+ g_slist_free (theme->custom_stylesheets);
+ theme->custom_stylesheets = NULL;
+
+ g_hash_table_destroy (theme->stylesheets_by_file);
+ g_hash_table_destroy (theme->files_by_stylesheet);
+
+ g_clear_object (&theme->application_stylesheet);
+ g_clear_object (&theme->theme_stylesheet);
+ g_clear_object (&theme->default_stylesheet);
+
+ if (theme->cascade)
+ {
+ cr_cascade_unref (theme->cascade);
+ theme->cascade = NULL;
+ }
+
+ G_OBJECT_CLASS (st_theme_parent_class)->finalize (object);
+}
+
+static void
+st_theme_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StTheme *theme = ST_THEME (object);
+
+ switch (prop_id)
+ {
+ case PROP_APPLICATION_STYLESHEET:
+ {
+ GFile *file = g_value_get_object (value);
+
+ if (!file_equal0 (file, theme->application_stylesheet))
+ {
+ g_clear_object (&theme->application_stylesheet);
+ if (file != NULL)
+ theme->application_stylesheet = g_object_ref (file);
+ }
+
+ break;
+ }
+ case PROP_THEME_STYLESHEET:
+ {
+ GFile *file = g_value_get_object (value);
+
+ if (!file_equal0 (file, theme->theme_stylesheet))
+ {
+ g_clear_object (&theme->theme_stylesheet);
+ if (file != NULL)
+ theme->theme_stylesheet = g_object_ref (file);
+ }
+
+ break;
+ }
+ case PROP_DEFAULT_STYLESHEET:
+ {
+ GFile *file = g_value_get_object (value);
+
+ if (!file_equal0 (file, theme->default_stylesheet))
+ {
+ g_clear_object (&theme->default_stylesheet);
+ if (file != NULL)
+ theme->default_stylesheet = g_object_ref (file);
+ }
+
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_theme_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StTheme *theme = ST_THEME (object);
+
+ switch (prop_id)
+ {
+ case PROP_APPLICATION_STYLESHEET:
+ g_value_set_object (value, theme->application_stylesheet);
+ break;
+ case PROP_THEME_STYLESHEET:
+ g_value_set_object (value, theme->theme_stylesheet);
+ break;
+ case PROP_DEFAULT_STYLESHEET:
+ g_value_set_object (value, theme->default_stylesheet);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * st_theme_new:
+ * @application_stylesheet: The highest priority stylesheet, representing application-specific
+ * styling; this is associated with the CSS "author" stylesheet, may be %NULL
+ * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
+ * this is associated with the CSS "user" stylesheet, may be %NULL
+ * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
+ * this is associated with the CSS "user agent" stylesheet, may be %NULL
+ *
+ * Returns: the newly created theme object
+ **/
+StTheme *
+st_theme_new (GFile *application_stylesheet,
+ GFile *theme_stylesheet,
+ GFile *default_stylesheet)
+{
+ StTheme *theme = g_object_new (ST_TYPE_THEME,
+ "application-stylesheet", application_stylesheet,
+ "theme-stylesheet", theme_stylesheet,
+ "default-stylesheet", default_stylesheet,
+ NULL);
+
+ return theme;
+}
+
+static gboolean
+string_in_list (GString *stryng,
+ GStrv list)
+{
+ gchar **it;
+
+ if (list == NULL)
+ return FALSE;
+
+ for (it = list; *it != NULL; it++)
+ {
+ if (!strqcmp (*it, stryng->str, stryng->len))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+pseudo_class_add_sel_matches_style (StTheme *a_this,
+ CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ GStrv node_pseudo_classes;
+
+ g_return_val_if_fail (a_this
+ && a_add_sel
+ && a_add_sel->content.pseudo
+ && a_add_sel->content.pseudo->name
+ && a_add_sel->content.pseudo->name->stryng
+ && a_add_sel->content.pseudo->name->stryng->str
+ && a_node, FALSE);
+
+ node_pseudo_classes = st_theme_node_get_pseudo_classes (a_node);
+
+ return string_in_list (a_add_sel->content.pseudo->name->stryng,
+ node_pseudo_classes);
+}
+
+/**
+ * class_add_sel_matches_style:
+ * @a_add_sel: The class additional selector to consider.
+ * @a_node: The style node to consider.
+ *
+ * Returns: %TRUE if the class additional selector matches
+ * the style node given in argument, %FALSE otherwise.
+ */
+static gboolean
+class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ GStrv element_classes;
+
+ g_return_val_if_fail (a_add_sel
+ && a_add_sel->type == CLASS_ADD_SELECTOR
+ && a_add_sel->content.class_name
+ && a_add_sel->content.class_name->stryng
+ && a_add_sel->content.class_name->stryng->str
+ && a_node, FALSE);
+
+ element_classes = st_theme_node_get_element_classes (a_node);
+
+ return string_in_list (a_add_sel->content.class_name->stryng,
+ element_classes);
+}
+
+/*
+ *@return TRUE if the additional attribute selector matches
+ *the current style node given in argument, FALSE otherwise.
+ *@param a_add_sel the additional attribute selector to consider.
+ *@param a_node the style node to consider.
+ */
+static gboolean
+id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ gboolean result = FALSE;
+ const char *id;
+
+ g_return_val_if_fail (a_add_sel
+ && a_add_sel->type == ID_ADD_SELECTOR
+ && a_add_sel->content.id_name
+ && a_add_sel->content.id_name->stryng
+ && a_add_sel->content.id_name->stryng->str
+ && a_node, FALSE);
+ g_return_val_if_fail (a_add_sel
+ && a_add_sel->type == ID_ADD_SELECTOR
+ && a_node, FALSE);
+
+ id = st_theme_node_get_element_id (a_node);
+
+ if (id != NULL)
+ {
+ if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
+ a_add_sel->content.id_name->stryng->len))
+ {
+ result = TRUE;
+ }
+ }
+
+ return result;
+}
+
+/**
+ *additional_selector_matches_style:
+ *Evaluates if a given additional selector matches an style node.
+ *@param a_add_sel the additional selector to consider.
+ *@param a_node the style node to consider.
+ *@return TRUE is a_add_sel matches a_node, FALSE otherwise.
+ */
+static gboolean
+additional_selector_matches_style (StTheme *a_this,
+ CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ CRAdditionalSel *cur_add_sel = NULL;
+
+ g_return_val_if_fail (a_add_sel, FALSE);
+
+ for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next)
+ {
+ switch (cur_add_sel->type)
+ {
+ case NO_ADD_SELECTOR:
+ return FALSE;
+ case CLASS_ADD_SELECTOR:
+ if (!class_add_sel_matches_style (cur_add_sel, a_node))
+ return FALSE;
+ break;
+ case ID_ADD_SELECTOR:
+ if (!id_add_sel_matches_style (cur_add_sel, a_node))
+ return FALSE;
+ break;
+ case ATTRIBUTE_ADD_SELECTOR:
+ g_warning ("Attribute selectors not supported");
+ return FALSE;
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
+ return FALSE;
+ break;
+ default:
+ g_warning ("Unhandled selector type %d", cur_add_sel->type);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+element_name_matches_type (const char *element_name,
+ GType element_type)
+{
+ if (element_type == G_TYPE_NONE)
+ {
+ return strcmp (element_name, "stage") == 0;
+ }
+ else
+ {
+ GType match_type = g_type_from_name (element_name);
+ if (match_type == G_TYPE_INVALID)
+ return FALSE;
+
+ return g_type_is_a (element_type, match_type);
+ }
+}
+
+/*
+ *Evaluate a selector (a simple selectors list) and says
+ *if it matches the style node given in parameter.
+ *The algorithm used here is the following:
+ *Walk the combinator separated list of simple selectors backward, starting
+ *from the end of the list. For each simple selector, looks if
+ *if matches the current style.
+ *
+ *@param a_this the selection engine.
+ *@param a_sel the simple selection list.
+ *@param a_node the style node.
+ *@param a_result out parameter. Set to true if the
+ *selector matches the style node, FALSE otherwise.
+ *@param a_recurse if set to TRUE, the function will walk to
+ *the next simple selector (after the evaluation of the current one)
+ *and recursively evaluate it. Must be usually set to TRUE unless you
+ *know what you are doing.
+ */
+static enum CRStatus
+sel_matches_style_real (StTheme *a_this,
+ CRSimpleSel *a_sel,
+ StThemeNode *a_node,
+ gboolean *a_result,
+ gboolean a_eval_sel_list_from_end,
+ gboolean a_recurse)
+{
+ CRSimpleSel *cur_sel = NULL;
+ StThemeNode *cur_node = NULL;
+ GType cur_type;
+
+ *a_result = FALSE;
+
+ if (a_eval_sel_list_from_end)
+ {
+ /*go and get the last simple selector of the list */
+ for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next)
+ ;
+ }
+ else
+ {
+ cur_sel = a_sel;
+ }
+
+ cur_node = a_node;
+ cur_type = st_theme_node_get_element_type (cur_node);
+
+ while (cur_sel)
+ {
+ if (((cur_sel->type_mask & TYPE_SELECTOR)
+ && (cur_sel->name
+ && cur_sel->name->stryng
+ && cur_sel->name->stryng->str)
+ &&
+ (element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
+ || (cur_sel->type_mask & UNIVERSAL_SELECTOR))
+ {
+ /*
+ *this simple selector
+ *matches the current style node
+ *Let's see if the preceding
+ *simple selectors also match
+ *their style node counterpart.
+ */
+ if (cur_sel->add_sel)
+ {
+ if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+ goto walk_a_step_in_expr;
+ else
+ goto done;
+ }
+ else
+ goto walk_a_step_in_expr;
+ }
+ if (!(cur_sel->type_mask & TYPE_SELECTOR)
+ && !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
+ {
+ if (!cur_sel->add_sel)
+ goto done;
+ if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+ goto walk_a_step_in_expr;
+ else
+ goto done;
+ }
+ else
+ {
+ goto done;
+ }
+
+ walk_a_step_in_expr:
+ if (a_recurse == FALSE)
+ {
+ *a_result = TRUE;
+ goto done;
+ }
+
+ /*
+ *here, depending on the combinator of cur_sel
+ *choose the axis of the element tree traversal
+ *and walk one step in the element tree.
+ */
+ if (!cur_sel->prev)
+ break;
+
+ switch (cur_sel->combinator)
+ {
+ case NO_COMBINATOR:
+ break;
+
+ case COMB_WS: /*descendant selector */
+ {
+ StThemeNode *n = NULL;
+
+ /*
+ *walk the element tree upward looking for a parent
+ *style that matches the preceding selector.
+ */
+ for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
+ {
+ enum CRStatus status;
+ gboolean matches = FALSE;
+
+ status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
+
+ if (status != CR_OK)
+ goto done;
+
+ if (matches)
+ {
+ cur_node = n;
+ cur_type = st_theme_node_get_element_type (cur_node);
+ break;
+ }
+ }
+
+ if (!n)
+ {
+ /*
+ *didn't find any ancestor that matches
+ *the previous simple selector.
+ */
+ goto done;
+ }
+ /*
+ *in this case, the preceding simple sel
+ *will have been interpreted twice, which
+ *is a cpu and mem waste ... I need to find
+ *another way to do this. Anyway, this is
+ *my first attempt to write this function and
+ *I am a bit clueless.
+ */
+ break;
+ }
+
+ case COMB_PLUS:
+ g_warning ("+ combinators are not supported");
+ goto done;
+
+ case COMB_GT:
+ cur_node = st_theme_node_get_parent (cur_node);
+ if (!cur_node)
+ goto done;
+ cur_type = st_theme_node_get_element_type (cur_node);
+ break;
+
+ default:
+ goto done;
+ }
+
+ cur_sel = cur_sel->prev;
+ }
+
+ /*
+ *if we reached this point, it means the selector matches
+ *the style node.
+ */
+ *a_result = TRUE;
+
+done:
+ return CR_OK;
+}
+
+static void
+add_matched_properties (StTheme *a_this,
+ CRStyleSheet *a_nodesheet,
+ StThemeNode *a_node,
+ GPtrArray *props)
+{
+ CRStatement *cur_stmt = NULL;
+ CRSelector *sel_list = NULL;
+ CRSelector *cur_sel = NULL;
+ gboolean matches = FALSE;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *walk through the list of statements and,
+ *get the selectors list inside the statements that
+ *contain some, and try to match our style node in these
+ *selectors lists.
+ */
+ for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next)
+ {
+ /*
+ *initialize the selector list in which we will
+ *really perform the search.
+ */
+ sel_list = NULL;
+
+ /*
+ *get the the damn selector list in
+ *which we have to look
+ */
+ switch (cur_stmt->type)
+ {
+ case RULESET_STMT:
+ if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
+ {
+ sel_list = cur_stmt->kind.ruleset->sel_list;
+ }
+ break;
+
+ case AT_IMPORT_RULE_STMT:
+ {
+ CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
+
+ if (import_rule->sheet == NULL)
+ {
+ GFile *file = NULL;
+
+ if (import_rule->url->stryng && import_rule->url->stryng->str)
+ {
+ file = _st_theme_resolve_url (a_this,
+ a_nodesheet,
+ import_rule->url->stryng->str);
+ import_rule->sheet = parse_stylesheet (file, NULL);
+ }
+
+ if (import_rule->sheet)
+ {
+ insert_stylesheet (a_this, file, import_rule->sheet);
+ /* refcount of stylesheets starts off at zero, so we don't need to unref! */
+ }
+ else
+ {
+ /* Set a marker to avoid repeatedly trying to parse a non-existent or
+ * broken stylesheet
+ */
+ import_rule->sheet = (CRStyleSheet *) - 1;
+ }
+
+ if (file)
+ g_object_unref (file);
+ }
+
+ if (import_rule->sheet != (CRStyleSheet *) - 1)
+ {
+ add_matched_properties (a_this, import_rule->sheet,
+ a_node, props);
+ }
+ }
+ break;
+ case AT_MEDIA_RULE_STMT:
+ case AT_RULE_STMT:
+ case AT_PAGE_RULE_STMT:
+ case AT_CHARSET_RULE_STMT:
+ case AT_FONT_FACE_RULE_STMT:
+ default:
+ break;
+ }
+
+ if (!sel_list)
+ continue;
+
+ /*
+ *now, we have a comma separated selector list to look in.
+ *let's walk it and try to match the style node
+ *on each item of the list.
+ */
+ for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next)
+ {
+ if (!cur_sel->simple_sel)
+ continue;
+
+ status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE);
+
+ if (status == CR_OK && matches)
+ {
+ CRDeclaration *cur_decl = NULL;
+
+ /* In order to sort the matching properties, we need to compute the
+ * specificity of the selector that actually matched this
+ * element. In a non-thread-safe fashion, we store it in the
+ * ruleset. (Fixing this would mean cut-and-pasting
+ * cr_simple_sel_compute_specificity(), and have no need for
+ * thread-safety anyways.)
+ *
+ * Once we've sorted the properties, the specificity no longer
+ * matters and it can be safely overridden.
+ */
+ cr_simple_sel_compute_specificity (cur_sel->simple_sel);
+
+ cur_stmt->specificity = cur_sel->simple_sel->specificity;
+
+ for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next)
+ g_ptr_array_add (props, cur_decl);
+ }
+ }
+ }
+}
+
+#define ORIGIN_OFFSET_IMPORTANT (NB_ORIGINS)
+#define ORIGIN_OFFSET_EXTENSION (NB_ORIGINS * 2)
+
+static inline int
+get_origin (const CRDeclaration * decl)
+{
+ enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
+ gboolean is_extension_sheet = GPOINTER_TO_UINT (decl->parent_statement->parent_sheet->app_data);
+
+ if (decl->important)
+ origin += ORIGIN_OFFSET_IMPORTANT;
+
+ if (is_extension_sheet)
+ origin += ORIGIN_OFFSET_EXTENSION;
+
+ return origin;
+}
+
+/* Order of comparison is so that higher priority statements compare after
+ * lower priority statements */
+static int
+compare_declarations (gconstpointer a,
+ gconstpointer b)
+{
+ /* g_ptr_array_sort() is broooken */
+ CRDeclaration *decl_a = *(CRDeclaration **) a;
+ CRDeclaration *decl_b = *(CRDeclaration **) b;
+
+ int origin_a = get_origin (decl_a);
+ int origin_b = get_origin (decl_b);
+
+ if (origin_a != origin_b)
+ return origin_a - origin_b;
+
+ if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity)
+ return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity;
+
+ return 0;
+}
+
+GPtrArray *
+_st_theme_get_matched_properties (StTheme *theme,
+ StThemeNode *node)
+{
+ enum CRStyleOrigin origin = 0;
+ CRStyleSheet *sheet = NULL;
+ GPtrArray *props = g_ptr_array_new ();
+ GSList *iter;
+
+ g_return_val_if_fail (ST_IS_THEME (theme), NULL);
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++)
+ {
+ sheet = cr_cascade_get_sheet (theme->cascade, origin);
+ if (!sheet)
+ continue;
+
+ add_matched_properties (theme, sheet, node, props);
+ }
+
+ for (iter = theme->custom_stylesheets; iter; iter = iter->next)
+ add_matched_properties (theme, iter->data, node, props);
+
+ /* We count on a stable sort here so that later declarations come
+ * after earlier declarations */
+ g_ptr_array_sort (props, compare_declarations);
+
+ return props;
+}
+
+/* Resolve an url from an url() reference in a stylesheet into a GFile,
+ * if possible. The resolution here is distinctly lame and
+ * will fail on many examples.
+ */
+GFile *
+_st_theme_resolve_url (StTheme *theme,
+ CRStyleSheet *base_stylesheet,
+ const char *url)
+{
+ char *scheme;
+ GFile *resource;
+
+ if ((scheme = g_uri_parse_scheme (url)))
+ {
+ g_free (scheme);
+ resource = g_file_new_for_uri (url);
+ }
+ else if (base_stylesheet != NULL)
+ {
+ GFile *base_file = NULL, *parent;
+
+ base_file = g_hash_table_lookup (theme->files_by_stylesheet, base_stylesheet);
+
+ /* This is an internal function, if we get here with
+ a bad @base_stylesheet we have a problem. */
+ g_assert (base_file);
+
+ parent = g_file_get_parent (base_file);
+ resource = g_file_resolve_relative_path (parent, url);
+
+ g_object_unref (parent);
+ }
+ else
+ {
+ resource = g_file_new_for_path (url);
+ }
+
+ return resource;
+}
diff --git a/src/st/st-theme.h b/src/st/st-theme.h
new file mode 100644
index 0000000..d3f242c
--- /dev/null
+++ b/src/st/st-theme.h
@@ -0,0 +1,51 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme.h: A set of CSS stylesheets used for rule matching
+ *
+ * Copyright 2008, 2009 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __ST_THEME_H__
+#define __ST_THEME_H__
+
+#include <glib-object.h>
+
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-theme
+ * @short_description: a set of stylesheets
+ *
+ * #StTheme holds a set of stylesheets. (The "cascade" of the name
+ * Cascading Stylesheets.) A #StTheme can be set to apply to all the actors
+ * in a stage using st_theme_context_set_theme().
+ */
+
+#define ST_TYPE_THEME (st_theme_get_type ())
+G_DECLARE_FINAL_TYPE (StTheme, st_theme, ST, THEME, GObject)
+
+StTheme *st_theme_new (GFile *application_stylesheet,
+ GFile *theme_stylesheet,
+ GFile *default_stylesheet);
+
+gboolean st_theme_load_stylesheet (StTheme *theme, GFile *file, GError **error);
+void st_theme_unload_stylesheet (StTheme *theme, GFile *file);
+GSList *st_theme_get_custom_stylesheets (StTheme *theme);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_H__ */
diff --git a/src/st/st-types.h b/src/st/st-types.h
new file mode 100644
index 0000000..d204041
--- /dev/null
+++ b/src/st/st-types.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __ST_TYPES_H__
+#define __ST_TYPES_H__
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-types
+ * @short_description: type definitions used throughout St
+ *
+ * Common types for StWidgets.
+ */
+
+typedef enum {
+ ST_ALIGN_START,
+ ST_ALIGN_MIDDLE,
+ ST_ALIGN_END
+} StAlign;
+
+typedef enum {
+ ST_BACKGROUND_SIZE_AUTO,
+ ST_BACKGROUND_SIZE_CONTAIN,
+ ST_BACKGROUND_SIZE_COVER,
+ ST_BACKGROUND_SIZE_FIXED
+} StBackgroundSize;
+
+G_END_DECLS
+
+#endif /* __ST_TYPES_H__ */
diff --git a/src/st/st-viewport.c b/src/st/st-viewport.c
new file mode 100644
index 0000000..e6b9127
--- /dev/null
+++ b/src/st/st-viewport.c
@@ -0,0 +1,600 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scrollable-wiget.c: a scrollable actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Muellner
+ * Copyright 2019 Endless, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Portions copied from Clutter:
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Matthew Allum <mallum@openedhand.com>
+ *
+ * Copyright (C) 2006 OpenedHand
+ * 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.
+ */
+
+/**
+ * SECTION:st-viewport
+ * @short_description: a scrollable container
+ *
+ * The #StViewport is a generic #StScrollable implementation.
+ *
+ */
+
+#include <stdlib.h>
+
+#include "st-viewport.h"
+
+#include "st-private.h"
+#include "st-scrollable.h"
+
+
+static void st_viewport_scrollable_interface_init (StScrollableInterface *iface);
+
+enum {
+ PROP_0,
+
+ PROP_CLIP_TO_VIEW,
+
+ N_PROPS,
+
+ /* StScrollable */
+ PROP_HADJUST,
+ PROP_VADJUST
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+typedef struct
+{
+ StAdjustment *hadjustment;
+ StAdjustment *vadjustment;
+ gboolean clip_to_view;
+} StViewportPrivate;
+
+G_DEFINE_TYPE_WITH_CODE (StViewport, st_viewport, ST_TYPE_WIDGET,
+ G_ADD_PRIVATE (StViewport)
+ G_IMPLEMENT_INTERFACE (ST_TYPE_SCROLLABLE,
+ st_viewport_scrollable_interface_init));
+
+/*
+ * StScrollable Interface Implementation
+ */
+static void
+adjustment_value_notify_cb (StAdjustment *adjustment,
+ GParamSpec *pspec,
+ StViewport *viewport)
+{
+ clutter_actor_invalidate_transform (CLUTTER_ACTOR (viewport));
+ clutter_actor_invalidate_paint_volume (CLUTTER_ACTOR (viewport));
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (viewport));
+}
+
+static void
+scrollable_set_adjustments (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment)
+{
+ StViewport *viewport = ST_VIEWPORT (scrollable);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ g_object_freeze_notify (G_OBJECT (scrollable));
+
+ if (hadjustment != priv->hadjustment)
+ {
+ if (priv->hadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->hadjustment,
+ adjustment_value_notify_cb,
+ scrollable);
+ g_object_unref (priv->hadjustment);
+ }
+
+ if (hadjustment)
+ {
+ g_object_ref (hadjustment);
+ g_signal_connect (hadjustment, "notify::value",
+ G_CALLBACK (adjustment_value_notify_cb),
+ scrollable);
+ }
+
+ priv->hadjustment = hadjustment;
+ g_object_notify (G_OBJECT (scrollable), "hadjustment");
+ }
+
+ if (vadjustment != priv->vadjustment)
+ {
+ if (priv->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->vadjustment,
+ adjustment_value_notify_cb,
+ scrollable);
+ g_object_unref (priv->vadjustment);
+ }
+
+ if (vadjustment)
+ {
+ g_object_ref (vadjustment);
+ g_signal_connect (vadjustment, "notify::value",
+ G_CALLBACK (adjustment_value_notify_cb),
+ scrollable);
+ }
+
+ priv->vadjustment = vadjustment;
+ g_object_notify (G_OBJECT (scrollable), "vadjustment");
+ }
+
+ g_object_thaw_notify (G_OBJECT (scrollable));
+}
+
+static void
+scrollable_get_adjustments (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment)
+{
+ StViewport *viewport = ST_VIEWPORT (scrollable);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ if (hadjustment)
+ *hadjustment = priv->hadjustment;
+
+ if (vadjustment)
+ *vadjustment = priv->vadjustment;
+}
+
+static void
+st_viewport_scrollable_interface_init (StScrollableInterface *iface)
+{
+ iface->set_adjustments = scrollable_set_adjustments;
+ iface->get_adjustments = scrollable_get_adjustments;
+}
+
+static void
+st_viewport_set_clip_to_view (StViewport *viewport,
+ gboolean clip_to_view)
+{
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ if (!!priv->clip_to_view != !!clip_to_view)
+ {
+ priv->clip_to_view = clip_to_view;
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (viewport));
+ g_object_notify_by_pspec (G_OBJECT (viewport), props[PROP_CLIP_TO_VIEW]);
+ }
+}
+
+static void
+st_viewport_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (ST_VIEWPORT (object));
+ StAdjustment *adjustment;
+
+ switch (property_id)
+ {
+ case PROP_HADJUST:
+ scrollable_get_adjustments (ST_SCROLLABLE (object), &adjustment, NULL);
+ g_value_set_object (value, adjustment);
+ break;
+
+ case PROP_VADJUST:
+ scrollable_get_adjustments (ST_SCROLLABLE (object), NULL, &adjustment);
+ g_value_set_object (value, adjustment);
+ break;
+
+ case PROP_CLIP_TO_VIEW:
+ g_value_set_boolean (value, priv->clip_to_view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_viewport_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StViewport *viewport = ST_VIEWPORT (object);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ switch (property_id)
+ {
+ case PROP_HADJUST:
+ scrollable_set_adjustments (ST_SCROLLABLE (object),
+ g_value_get_object (value),
+ priv->vadjustment);
+ break;
+
+ case PROP_VADJUST:
+ scrollable_set_adjustments (ST_SCROLLABLE (object),
+ priv->hadjustment,
+ g_value_get_object (value));
+ break;
+
+ case PROP_CLIP_TO_VIEW:
+ st_viewport_set_clip_to_view (viewport, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_viewport_dispose (GObject *object)
+{
+ StViewport *viewport = ST_VIEWPORT (object);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ g_clear_object (&priv->hadjustment);
+ g_clear_object (&priv->vadjustment);
+
+ G_OBJECT_CLASS (st_viewport_parent_class)->dispose (object);
+}
+
+static void
+st_viewport_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterLayoutManager *layout = clutter_actor_get_layout_manager (actor);
+ ClutterActorBox viewport_box;
+ ClutterActorBox content_box;
+ float avail_width, avail_height;
+ float min_width, natural_width;
+ float min_height, natural_height;
+
+ st_theme_node_get_content_box (theme_node, box, &viewport_box);
+ clutter_actor_box_get_size (&viewport_box, &avail_width, &avail_height);
+
+ clutter_layout_manager_get_preferred_width (layout, CLUTTER_CONTAINER (actor),
+ avail_height,
+ &min_width, &natural_width);
+ clutter_layout_manager_get_preferred_height (layout, CLUTTER_CONTAINER (actor),
+ MAX (avail_width, min_width),
+ &min_height, &natural_height);
+
+ /* Because StViewport implements StScrollable, the allocation box passed here
+ * may not match the minimum sizes reported by the layout manager. When that
+ * happens, the content box needs to be adjusted to match the reported minimum
+ * sizes before being passed to clutter_layout_manager_allocate() */
+ clutter_actor_set_allocation (actor, box);
+
+ content_box = viewport_box;
+ if (priv->hadjustment)
+ content_box.x2 += MAX (0, min_width - avail_width);
+ if (priv->vadjustment)
+ content_box.y2 += MAX (0, min_height - avail_height);
+
+ clutter_layout_manager_allocate (layout, CLUTTER_CONTAINER (actor),
+ &content_box);
+
+ /* update adjustments for scrolling */
+ if (priv->vadjustment)
+ {
+ double prev_value;
+
+ g_object_set (G_OBJECT (priv->vadjustment),
+ "lower", 0.0,
+ "upper", MAX (min_height, avail_height),
+ "page-size", avail_height,
+ "step-increment", avail_height / 6,
+ "page-increment", avail_height - avail_height / 6,
+ NULL);
+
+ prev_value = st_adjustment_get_value (priv->vadjustment);
+ st_adjustment_set_value (priv->vadjustment, prev_value);
+ }
+
+ if (priv->hadjustment)
+ {
+ double prev_value;
+
+ g_object_set (G_OBJECT (priv->hadjustment),
+ "lower", 0.0,
+ "upper", MAX (min_width, avail_width),
+ "page-size", avail_width,
+ "step-increment", avail_width / 6,
+ "page-increment", avail_width - avail_width / 6,
+ NULL);
+
+ prev_value = st_adjustment_get_value (priv->hadjustment);
+ st_adjustment_set_value (priv->hadjustment, prev_value);
+ }
+}
+
+static double
+get_hadjustment_value (StViewport *viewport)
+{
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ ClutterTextDirection direction;
+ double x, upper, page_size;
+
+ if (!priv->hadjustment)
+ return 0;
+
+ st_adjustment_get_values (priv->hadjustment,
+ &x, NULL, &upper, NULL, NULL, &page_size);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (viewport));
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ return upper - page_size - x;
+
+ return x;
+}
+
+static void
+st_viewport_apply_transform (ClutterActor *actor,
+ graphene_matrix_t *matrix)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ ClutterActorClass *parent_class =
+ CLUTTER_ACTOR_CLASS (st_viewport_parent_class);
+ graphene_point3d_t p = GRAPHENE_POINT3D_INIT_ZERO;
+
+ if (priv->hadjustment)
+ p.x = -(int)get_hadjustment_value (viewport);
+
+ if (priv->vadjustment)
+ p.y = -(int)st_adjustment_get_value (priv->vadjustment);
+
+ graphene_matrix_translate (matrix, &p);
+
+ parent_class->apply_transform (actor, matrix);
+}
+
+/* If we are translated, then we need to translate back before chaining
+ * up or the background and borders will be drawn in the wrong place */
+static void
+get_border_paint_offsets (StViewport *viewport,
+ int *x,
+ int *y)
+{
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+
+ if (priv->hadjustment)
+ *x = get_hadjustment_value (viewport);
+ else
+ *x = 0;
+
+ if (priv->vadjustment)
+ *y = st_adjustment_get_value (priv->vadjustment);
+ else
+ *y = 0;
+}
+
+
+static void
+st_viewport_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ int x, y;
+ ClutterActorBox allocation_box;
+ ClutterActorBox content_box;
+ ClutterActor *child;
+ CoglFramebuffer *fb = clutter_paint_context_get_framebuffer (paint_context);
+
+ get_border_paint_offsets (viewport, &x, &y);
+ if (x != 0 || y != 0)
+ {
+ cogl_framebuffer_push_matrix (fb);
+ cogl_framebuffer_translate (fb, x, y, 0);
+ }
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (x != 0 || y != 0)
+ cogl_framebuffer_pop_matrix (fb);
+
+ if (clutter_actor_get_n_children (actor) == 0)
+ return;
+
+ clutter_actor_get_allocation_box (actor, &allocation_box);
+ st_theme_node_get_content_box (theme_node, &allocation_box, &content_box);
+
+ content_box.x1 += x;
+ content_box.y1 += y;
+ content_box.x2 += x;
+ content_box.y2 += y;
+
+ /* The content area forms the viewport into the scrolled contents, while
+ * the borders and background stay in place; after drawing the borders and
+ * background, we clip to the content area */
+ if (priv->clip_to_view && (priv->hadjustment || priv->vadjustment))
+ {
+ cogl_framebuffer_push_rectangle_clip (fb,
+ (int)content_box.x1,
+ (int)content_box.y1,
+ (int)content_box.x2,
+ (int)content_box.y2);
+ }
+
+ for (child = clutter_actor_get_first_child (actor);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ clutter_actor_paint (child, paint_context);
+
+ if (priv->clip_to_view && (priv->hadjustment || priv->vadjustment))
+ cogl_framebuffer_pop_clip (fb);
+}
+
+static void
+st_viewport_pick (ClutterActor *actor,
+ ClutterPickContext *pick_context)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ int x, y;
+ g_autoptr (ClutterActorBox) allocation_box = NULL;
+ ClutterActorBox content_box;
+ ClutterActor *child;
+
+ CLUTTER_ACTOR_CLASS (st_viewport_parent_class)->pick (actor, pick_context);
+
+ if (clutter_actor_get_n_children (actor) == 0)
+ return;
+
+ g_object_get (actor, "allocation", &allocation_box, NULL);
+ st_theme_node_get_content_box (theme_node, allocation_box, &content_box);
+
+ get_border_paint_offsets (viewport, &x, &y);
+
+ content_box.x1 += x;
+ content_box.y1 += y;
+ content_box.x2 += x;
+ content_box.y2 += y;
+
+ if (priv->hadjustment || priv->vadjustment)
+ clutter_pick_context_push_clip (pick_context, &content_box);
+
+ for (child = clutter_actor_get_first_child (actor);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ clutter_actor_pick (child, pick_context);
+
+ if (priv->hadjustment || priv->vadjustment)
+ clutter_pick_context_pop_clip (pick_context);
+}
+
+static gboolean
+st_viewport_get_paint_volume (ClutterActor *actor,
+ ClutterPaintVolume *volume)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox allocation_box;
+ ClutterActorBox content_box;
+ int x, y;
+
+ /* Setting the paint volume does not make sense when we don't have any allocation */
+ if (!clutter_actor_has_allocation (actor))
+ return FALSE;
+
+ if (!priv->clip_to_view)
+ return CLUTTER_ACTOR_CLASS (st_viewport_parent_class)->get_paint_volume (actor, volume);
+
+ /* When have an adjustment we are clipped to the content box, so base
+ * our paint volume on that. */
+ if (priv->hadjustment || priv->vadjustment)
+ {
+ double width, height;
+
+ clutter_actor_get_allocation_box (actor, &allocation_box);
+ st_theme_node_get_content_box (theme_node, &allocation_box, &content_box);
+
+ width = content_box.x2 - content_box.x1;
+ height = content_box.y2 - content_box.y1;
+
+ clutter_paint_volume_set_width (volume, width);
+ clutter_paint_volume_set_height (volume, height);
+ }
+ else if (!CLUTTER_ACTOR_CLASS (st_viewport_parent_class)->get_paint_volume (actor, volume))
+ {
+ return FALSE;
+ }
+
+ /* When scrolled, st_viewport_apply_transform() includes the scroll offset
+ * and affects paint volumes. This is right for our children, but our paint volume
+ * is determined by our allocation and borders and doesn't scroll, so we need
+ * to reverse-compensate here, the same as we do when painting.
+ */
+ get_border_paint_offsets (viewport, &x, &y);
+ if (x != 0 || y != 0)
+ {
+ graphene_point3d_t origin;
+
+ clutter_paint_volume_get_origin (volume, &origin);
+ origin.x += x;
+ origin.y += y;
+ clutter_paint_volume_set_origin (volume, &origin);
+ }
+
+ return TRUE;
+}
+
+static void
+st_viewport_class_init (StViewportClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ object_class->get_property = st_viewport_get_property;
+ object_class->set_property = st_viewport_set_property;
+ object_class->dispose = st_viewport_dispose;
+
+ actor_class->allocate = st_viewport_allocate;
+ actor_class->apply_transform = st_viewport_apply_transform;
+
+ actor_class->paint = st_viewport_paint;
+ actor_class->get_paint_volume = st_viewport_get_paint_volume;
+ actor_class->pick = st_viewport_pick;
+
+ props[PROP_CLIP_TO_VIEW] =
+ g_param_spec_boolean ("clip-to-view",
+ "Clip to view",
+ "Clip to view",
+ TRUE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /* StScrollable properties */
+ g_object_class_override_property (object_class,
+ PROP_HADJUST,
+ "hadjustment");
+
+ g_object_class_override_property (object_class,
+ PROP_VADJUST,
+ "vadjustment");
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_viewport_init (StViewport *self)
+{
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (self);
+
+ priv->clip_to_view = TRUE;
+}
diff --git a/src/st/st-viewport.h b/src/st/st-viewport.h
new file mode 100644
index 0000000..8f0b16b
--- /dev/null
+++ b/src/st/st-viewport.h
@@ -0,0 +1,40 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-viewport.h: viewport actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2019 Endless, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#pragma once
+
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_VIEWPORT (st_viewport_get_type())
+G_DECLARE_DERIVABLE_TYPE (StViewport, st_viewport, ST, VIEWPORT, StWidget)
+
+struct _StViewportClass
+{
+ StWidgetClass parent_class;
+};
+
+G_END_DECLS
diff --git a/src/st/st-widget-accessible.h b/src/st/st-widget-accessible.h
new file mode 100644
index 0000000..c60f778
--- /dev/null
+++ b/src/st/st-widget-accessible.h
@@ -0,0 +1,76 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-widget-accessible.h: Accessible object for StWidget
+ *
+ * Copyright 2010 Igalia, S.L.
+ * Author: Alejandro Piñeiro Iglesias <apinheiro@igalia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_WIDGET_ACCESSIBLE_H__
+#define __ST_WIDGET_ACCESSIBLE_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-widget.h>
+#include <cally/cally.h>
+
+#define ST_TYPE_WIDGET_ACCESSIBLE st_widget_accessible_get_type ()
+
+#define ST_WIDGET_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ ST_TYPE_WIDGET_ACCESSIBLE, StWidgetAccessible))
+
+#define ST_IS_WIDGET_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ ST_TYPE_WIDGET_ACCESSIBLE))
+
+#define ST_WIDGET_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ ST_TYPE_WIDGET_ACCESSIBLE, StWidgetAccessibleClass))
+
+#define ST_IS_WIDGET_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ ST_TYPE_WIDGET_ACCESSIBLE))
+
+#define ST_WIDGET_ACCESSIBLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ ST_TYPE_WIDGET_ACCESSIBLE, StWidgetAccessibleClass))
+
+typedef struct _StWidgetAccessible StWidgetAccessible;
+typedef struct _StWidgetAccessibleClass StWidgetAccessibleClass;
+typedef struct _StWidgetAccessiblePrivate StWidgetAccessiblePrivate;
+
+struct _StWidgetAccessible
+{
+ CallyActor parent;
+
+ /*< private >*/
+ StWidgetAccessiblePrivate *priv;
+};
+
+struct _StWidgetAccessibleClass
+{
+ CallyActorClass parent_class;
+};
+
+GType st_widget_accessible_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __ST_WIDGET_ACCESSIBLE_H__ */
diff --git a/src/st/st-widget.c b/src/st/st-widget.c
new file mode 100644
index 0000000..31c400b
--- /dev/null
+++ b/src/st/st-widget.c
@@ -0,0 +1,3049 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-widget.c: Base class for St actors
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Adel Gadllah
+ * Copyright 2012 Igalia, S.L.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include <clutter/clutter.h>
+
+#include "st-widget.h"
+
+#include "st-label.h"
+#include "st-private.h"
+#include "st-settings.h"
+#include "st-texture-cache.h"
+#include "st-theme-context.h"
+#include "st-theme-node-transition.h"
+#include "st-theme-node-private.h"
+#include "st-drawing-area.h"
+
+#include "st-widget-accessible.h"
+
+#include <atk/atk-enum-types.h>
+
+/* This is set in stone and also hard-coded in GDK. */
+#define VIRTUAL_CORE_POINTER_ID 2
+
+/*
+ * Forward declaration for sake of StWidgetChild
+ */
+typedef struct _StWidgetPrivate StWidgetPrivate;
+struct _StWidgetPrivate
+{
+ StThemeNode *theme_node;
+ gchar *pseudo_class;
+ gchar *style_class;
+ gchar *inline_style;
+
+ StThemeNodeTransition *transition_animation;
+
+ guint is_style_dirty : 1;
+ guint first_child_dirty : 1;
+ guint last_child_dirty : 1;
+ guint draw_bg_color : 1;
+ guint draw_border_internal : 1;
+ guint track_hover : 1;
+ guint hover : 1;
+ guint can_focus : 1;
+
+ gulong texture_file_changed_id;
+ guint update_child_styles_id;
+
+ AtkObject *accessible;
+ AtkRole accessible_role;
+ AtkStateSet *local_state_set;
+
+ ClutterActor *label_actor;
+ gchar *accessible_name;
+
+ StWidget *last_visible_child;
+ StWidget *first_visible_child;
+
+ StThemeNodePaintState paint_states[2];
+ int current_paint_state : 2;
+};
+
+/**
+ * SECTION:st-widget
+ * @short_description: Base class for stylable actors
+ *
+ * #StWidget is a simple abstract class on top of #ClutterActor. It
+ * provides basic themeing properties.
+ *
+ * Actors in the St library should subclass #StWidget if they plan
+ * to obey to a certain #StStyle.
+ */
+
+enum
+{
+ PROP_0,
+
+ PROP_PSEUDO_CLASS,
+ PROP_STYLE_CLASS,
+ PROP_STYLE,
+ PROP_TRACK_HOVER,
+ PROP_HOVER,
+ PROP_CAN_FOCUS,
+ PROP_LABEL_ACTOR,
+ PROP_ACCESSIBLE_ROLE,
+ PROP_ACCESSIBLE_NAME,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ STYLE_CHANGED,
+ POPUP_MENU,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StWidget, st_widget, CLUTTER_TYPE_ACTOR);
+#define ST_WIDGET_PRIVATE(w) ((StWidgetPrivate *)st_widget_get_instance_private (w))
+
+static void st_widget_recompute_style (StWidget *widget,
+ StThemeNode *old_theme_node);
+static gboolean st_widget_real_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction);
+
+static AtkObject * st_widget_get_accessible (ClutterActor *actor);
+static gboolean st_widget_has_accessible (ClutterActor *actor);
+
+static void
+st_widget_update_insensitive (StWidget *widget)
+{
+ if (clutter_actor_get_reactive (CLUTTER_ACTOR (widget)))
+ st_widget_remove_style_pseudo_class (widget, "insensitive");
+ else
+ st_widget_add_style_pseudo_class (widget, "insensitive");
+}
+
+static void
+st_widget_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StWidget *actor = ST_WIDGET (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PSEUDO_CLASS:
+ st_widget_set_style_pseudo_class (actor, g_value_get_string (value));
+ break;
+
+ case PROP_STYLE_CLASS:
+ st_widget_set_style_class_name (actor, g_value_get_string (value));
+ break;
+
+ case PROP_STYLE:
+ st_widget_set_style (actor, g_value_get_string (value));
+ break;
+
+ case PROP_TRACK_HOVER:
+ st_widget_set_track_hover (actor, g_value_get_boolean (value));
+ break;
+
+ case PROP_HOVER:
+ st_widget_set_hover (actor, g_value_get_boolean (value));
+ break;
+
+ case PROP_CAN_FOCUS:
+ st_widget_set_can_focus (actor, g_value_get_boolean (value));
+ break;
+
+ case PROP_LABEL_ACTOR:
+ st_widget_set_label_actor (actor, g_value_get_object (value));
+ break;
+
+ case PROP_ACCESSIBLE_ROLE:
+ st_widget_set_accessible_role (actor, g_value_get_enum (value));
+ break;
+
+ case PROP_ACCESSIBLE_NAME:
+ st_widget_set_accessible_name (actor, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_widget_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StWidget *actor = ST_WIDGET (gobject);
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_PSEUDO_CLASS:
+ g_value_set_string (value, priv->pseudo_class);
+ break;
+
+ case PROP_STYLE_CLASS:
+ g_value_set_string (value, priv->style_class);
+ break;
+
+ case PROP_STYLE:
+ g_value_set_string (value, priv->inline_style);
+ break;
+
+ case PROP_TRACK_HOVER:
+ g_value_set_boolean (value, priv->track_hover);
+ break;
+
+ case PROP_HOVER:
+ g_value_set_boolean (value, priv->hover);
+ break;
+
+ case PROP_CAN_FOCUS:
+ g_value_set_boolean (value, priv->can_focus);
+ break;
+
+ case PROP_LABEL_ACTOR:
+ g_value_set_object (value, priv->label_actor);
+ break;
+
+ case PROP_ACCESSIBLE_ROLE:
+ g_value_set_enum (value, st_widget_get_accessible_role (actor));
+ break;
+
+ case PROP_ACCESSIBLE_NAME:
+ g_value_set_string (value, priv->accessible_name);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_widget_constructed (GObject *gobject)
+{
+ G_OBJECT_CLASS (st_widget_parent_class)->constructed (gobject);
+
+ st_widget_update_insensitive (ST_WIDGET (gobject));
+}
+
+static void
+st_widget_remove_transition (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->transition_animation)
+ {
+ g_object_run_dispose (G_OBJECT (priv->transition_animation));
+ g_object_unref (priv->transition_animation);
+ priv->transition_animation = NULL;
+ }
+}
+
+static void
+next_paint_state (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ priv->current_paint_state = (priv->current_paint_state + 1) % G_N_ELEMENTS (priv->paint_states);
+}
+
+static StThemeNodePaintState *
+current_paint_state (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ return &priv->paint_states[priv->current_paint_state];
+}
+
+static void
+st_widget_texture_cache_changed (StTextureCache *cache,
+ GFile *file,
+ gpointer user_data)
+{
+ StWidget *actor = ST_WIDGET (user_data);
+ StWidgetPrivate *priv = st_widget_get_instance_private (actor);
+ gboolean changed = FALSE;
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ {
+ StThemeNodePaintState *paint_state = &priv->paint_states[i];
+ changed |= st_theme_node_paint_state_invalidate_for_file (paint_state, file);
+ }
+
+ if (changed && clutter_actor_is_mapped (CLUTTER_ACTOR (actor)))
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (actor));
+}
+
+static void
+st_widget_dispose (GObject *gobject)
+{
+ StWidget *actor = ST_WIDGET (gobject);
+ StWidgetPrivate *priv = st_widget_get_instance_private (actor);
+
+ g_clear_pointer (&priv->theme_node, g_object_unref);
+
+ st_widget_remove_transition (actor);
+
+ g_clear_pointer (&priv->label_actor, g_object_unref);
+
+ g_clear_signal_handler (&priv->texture_file_changed_id,
+ st_texture_cache_get_default ());
+
+ g_clear_object (&priv->first_visible_child);
+ g_clear_object (&priv->last_visible_child);
+
+ G_OBJECT_CLASS (st_widget_parent_class)->dispose (gobject);
+
+ g_clear_handle_id (&priv->update_child_styles_id, g_source_remove);
+}
+
+static void
+st_widget_finalize (GObject *gobject)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (gobject));
+ guint i;
+
+ g_free (priv->style_class);
+ g_free (priv->pseudo_class);
+ g_object_unref (priv->local_state_set);
+ g_free (priv->accessible_name);
+ g_free (priv->inline_style);
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ st_theme_node_paint_state_free (&priv->paint_states[i]);
+
+ G_OBJECT_CLASS (st_widget_parent_class)->finalize (gobject);
+}
+
+
+static void
+st_widget_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_width (theme_node, &for_height);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_width (self, for_height, min_width_p, natural_width_p);
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_widget_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_height (self, for_width, min_height_p, natural_height_p);
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_widget_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box;
+
+ /* Note that we can't just chain up to clutter_actor_real_allocate --
+ * Clutter does some dirty tricks for backwards compatibility.
+ * Clutter also passes the actor's allocation directly to the layout
+ * manager, meaning that we can't modify it for children only.
+ */
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ /* If we've chained up to here, we want to allocate the children using the
+ * currently installed layout manager */
+ clutter_layout_manager_allocate (clutter_actor_get_layout_manager (actor),
+ CLUTTER_CONTAINER (actor),
+ &content_box);
+}
+
+/**
+ * st_widget_paint_background:
+ * @widget: The #StWidget
+ *
+ * Paint the background of the widget. This is meant to be called by
+ * subclasses of StWidget that need to paint the background without
+ * painting children.
+ */
+void
+st_widget_paint_background (StWidget *widget,
+ ClutterPaintContext *paint_context)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ CoglFramebuffer *framebuffer;
+ StThemeNode *theme_node;
+ ClutterActorBox allocation;
+ float resource_scale;
+ guint8 opacity;
+
+ resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (widget));
+
+ framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+ theme_node = st_widget_get_theme_node (widget);
+
+ clutter_actor_get_allocation_box (CLUTTER_ACTOR (widget), &allocation);
+
+ opacity = clutter_actor_get_paint_opacity (CLUTTER_ACTOR (widget));
+
+ if (priv->transition_animation)
+ st_theme_node_transition_paint (priv->transition_animation,
+ framebuffer,
+ &allocation,
+ opacity,
+ resource_scale);
+ else
+ st_theme_node_paint (theme_node,
+ current_paint_state (widget),
+ framebuffer,
+ &allocation,
+ opacity,
+ resource_scale);
+}
+
+static void
+st_widget_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ /* Chain up so we paint children. */
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->paint (actor, paint_context);
+}
+
+static void
+st_widget_parent_set (ClutterActor *widget,
+ ClutterActor *old_parent)
+{
+ StWidget *self = ST_WIDGET (widget);
+ ClutterActorClass *parent_class;
+
+ parent_class = CLUTTER_ACTOR_CLASS (st_widget_parent_class);
+ if (parent_class->parent_set)
+ parent_class->parent_set (widget, old_parent);
+
+ st_widget_style_changed (self);
+}
+
+static void
+st_widget_map (ClutterActor *actor)
+{
+ StWidget *self = ST_WIDGET (actor);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->map (actor);
+
+ st_widget_ensure_style (self);
+}
+
+static void
+st_widget_unmap (ClutterActor *actor)
+{
+ StWidget *self = ST_WIDGET (actor);
+ StWidgetPrivate *priv = st_widget_get_instance_private (self);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->unmap (actor);
+
+ st_widget_remove_transition (self);
+
+ if (priv->track_hover && priv->hover)
+ st_widget_set_hover (self, FALSE);
+}
+
+static void
+notify_children_of_style_change (ClutterActor *self)
+{
+ ClutterActorIter iter;
+ ClutterActor *actor;
+
+ clutter_actor_iter_init (&iter, self);
+ while (clutter_actor_iter_next (&iter, &actor))
+ {
+ if (ST_IS_WIDGET (actor))
+ st_widget_style_changed (ST_WIDGET (actor));
+ else
+ notify_children_of_style_change (actor);
+ }
+}
+
+static void
+st_widget_real_style_changed (StWidget *self)
+{
+ clutter_actor_queue_redraw ((ClutterActor *) self);
+}
+
+void
+st_widget_style_changed (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ StThemeNode *old_theme_node = NULL;
+
+ priv->is_style_dirty = TRUE;
+ if (priv->theme_node)
+ {
+ old_theme_node = priv->theme_node;
+ priv->theme_node = NULL;
+ }
+
+ /* update the style only if we are mapped */
+ if (clutter_actor_is_mapped (CLUTTER_ACTOR (widget)))
+ st_widget_recompute_style (widget, old_theme_node);
+
+ /* Descend through all children. If the actor is not mapped,
+ * children will clear their theme node without recomputing style.
+ */
+ notify_children_of_style_change (CLUTTER_ACTOR (widget));
+
+ if (old_theme_node)
+ g_object_unref (old_theme_node);
+}
+
+static void
+on_theme_context_changed (StThemeContext *context,
+ ClutterStage *stage)
+{
+ notify_children_of_style_change (CLUTTER_ACTOR (stage));
+}
+
+static StThemeNode *
+get_root_theme_node (ClutterStage *stage)
+{
+ StThemeContext *context = st_theme_context_get_for_stage (stage);
+
+ if (!g_object_get_data (G_OBJECT (context), "st-theme-initialized"))
+ {
+ g_object_set_data (G_OBJECT (context), "st-theme-initialized", GUINT_TO_POINTER (1));
+ g_signal_connect (G_OBJECT (context), "changed",
+ G_CALLBACK (on_theme_context_changed), stage);
+ }
+
+ return st_theme_context_get_root_node (context);
+}
+
+/**
+ * st_widget_get_theme_node:
+ * @widget: a #StWidget
+ *
+ * Gets the theme node holding style information for the widget.
+ * The theme node is used to access standard and custom CSS
+ * properties of the widget.
+ *
+ * Note: it is a fatal error to call this on a widget that is
+ * not been added to a stage.
+ *
+ * Returns: (transfer none): the theme node for the widget.
+ * This is owned by the widget. When attributes of the widget
+ * or the environment that affect the styling change (for example
+ * the style_class property of the widget), it will be recreated,
+ * and the ::style-changed signal will be emitted on the widget.
+ */
+StThemeNode *
+st_widget_get_theme_node (StWidget *widget)
+{
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->theme_node == NULL)
+ {
+ StThemeContext *context;
+ StThemeNode *tmp_node;
+ StThemeNode *parent_node = NULL;
+ ClutterStage *stage = NULL;
+ ClutterActor *parent;
+ char *pseudo_class, *direction_pseudo_class;
+
+ parent = clutter_actor_get_parent (CLUTTER_ACTOR (widget));
+ while (parent != NULL)
+ {
+ if (parent_node == NULL && ST_IS_WIDGET (parent))
+ parent_node = st_widget_get_theme_node (ST_WIDGET (parent));
+ else if (CLUTTER_IS_STAGE (parent))
+ stage = CLUTTER_STAGE (parent);
+
+ parent = clutter_actor_get_parent (parent);
+ }
+
+ if (stage == NULL)
+ {
+ g_autofree char *desc = st_describe_actor (CLUTTER_ACTOR (widget));
+
+ g_critical ("st_widget_get_theme_node called on the widget %s which is not in the stage.",
+ desc);
+
+ return g_object_new (ST_TYPE_THEME_NODE, NULL);
+ }
+
+ if (parent_node == NULL)
+ parent_node = get_root_theme_node (CLUTTER_STAGE (stage));
+
+ /* Always append a "magic" pseudo class indicating the text
+ * direction, to allow to adapt the CSS when necessary without
+ * requiring separate style sheets.
+ */
+ if (clutter_actor_get_text_direction (CLUTTER_ACTOR (widget)) == CLUTTER_TEXT_DIRECTION_RTL)
+ direction_pseudo_class = (char *)"rtl";
+ else
+ direction_pseudo_class = (char *)"ltr";
+
+ if (priv->pseudo_class)
+ pseudo_class = g_strconcat(priv->pseudo_class, " ",
+ direction_pseudo_class, NULL);
+ else
+ pseudo_class = direction_pseudo_class;
+
+ context = st_theme_context_get_for_stage (stage);
+ tmp_node = st_theme_node_new (context, parent_node, NULL,
+ G_OBJECT_TYPE (widget),
+ clutter_actor_get_name (CLUTTER_ACTOR (widget)),
+ priv->style_class,
+ pseudo_class,
+ priv->inline_style);
+
+ if (pseudo_class != direction_pseudo_class)
+ g_free (pseudo_class);
+
+ priv->theme_node = g_object_ref (st_theme_context_intern_node (context,
+ tmp_node));
+ g_object_unref (tmp_node);
+ }
+
+ return priv->theme_node;
+}
+
+/**
+ * st_widget_peek_theme_node:
+ * @widget: a #StWidget
+ *
+ * Returns the theme node for the widget if it has already been
+ * computed, %NULL if the widget hasn't been added to a stage or the theme
+ * node hasn't been computed. If %NULL is returned, then ::style-changed
+ * will be reliably emitted before the widget is allocated or painted.
+ *
+ * Returns: (transfer none): the theme node for the widget.
+ * This is owned by the widget. When attributes of the widget
+ * or the environment that affect the styling change (for example
+ * the style_class property of the widget), it will be recreated,
+ * and the ::style-changed signal will be emitted on the widget.
+ */
+StThemeNode *
+st_widget_peek_theme_node (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ return ST_WIDGET_PRIVATE (widget)->theme_node;
+}
+
+static gboolean
+st_widget_enter (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (actor));
+
+ if (priv->track_hover)
+ {
+ ClutterStage *stage;
+ ClutterActor *target;
+
+ stage = clutter_event_get_stage ((ClutterEvent *) event);
+ target = clutter_stage_get_event_actor (stage, (ClutterEvent *) event);
+
+ if (clutter_actor_contains (actor, target))
+ st_widget_set_hover (ST_WIDGET (actor), TRUE);
+ else
+ {
+ /* The widget has a grab and is being told about an
+ * enter-event outside its hierarchy. Hopefully we already
+ * got a leave-event, but if not, handle it now.
+ */
+ st_widget_set_hover (ST_WIDGET (actor), FALSE);
+ }
+ }
+
+ if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event)
+ return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event (actor, event);
+ else
+ return FALSE;
+}
+
+static gboolean
+st_widget_leave (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (actor));
+
+ if (priv->track_hover)
+ {
+ if (!event->related || !clutter_actor_contains (actor, event->related))
+ st_widget_set_hover (ST_WIDGET (actor), FALSE);
+ }
+
+ if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event)
+ return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event (actor, event);
+ else
+ return FALSE;
+}
+
+static void
+st_widget_key_focus_in (ClutterActor *actor)
+{
+ StWidget *widget = ST_WIDGET (actor);
+
+ st_widget_add_style_pseudo_class (widget, "focus");
+}
+
+static void
+st_widget_key_focus_out (ClutterActor *actor)
+{
+ StWidget *widget = ST_WIDGET (actor);
+
+ st_widget_remove_style_pseudo_class (widget, "focus");
+}
+
+static gboolean
+st_widget_key_press_event (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ if (event->keyval == CLUTTER_KEY_Menu ||
+ (event->keyval == CLUTTER_KEY_F10 &&
+ (event->modifier_state & CLUTTER_SHIFT_MASK)))
+ {
+ st_widget_popup_menu (ST_WIDGET (actor));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_widget_get_paint_volume (ClutterActor *self,
+ ClutterPaintVolume *volume)
+{
+ ClutterActorBox paint_box, alloc_box;
+ StThemeNode *theme_node;
+ StWidgetPrivate *priv;
+ graphene_point3d_t origin;
+
+ /* Setting the paint volume does not make sense when we don't have any allocation */
+ if (!clutter_actor_has_allocation (self))
+ return FALSE;
+
+ priv = st_widget_get_instance_private (ST_WIDGET (self));
+
+ theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ clutter_actor_get_allocation_box (self, &alloc_box);
+
+ if (priv->transition_animation)
+ st_theme_node_transition_get_paint_box (priv->transition_animation,
+ &alloc_box, &paint_box);
+ else
+ st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box);
+
+ origin.x = paint_box.x1 - alloc_box.x1;
+ origin.y = paint_box.y1 - alloc_box.y1;
+ origin.z = 0.0f;
+
+ clutter_paint_volume_set_origin (volume, &origin);
+ clutter_paint_volume_set_width (volume, paint_box.x2 - paint_box.x1);
+ clutter_paint_volume_set_height (volume, paint_box.y2 - paint_box.y1);
+
+ if (!clutter_actor_get_clip_to_allocation (self))
+ {
+ ClutterActor *child;
+ StShadow *shadow_spec = st_theme_node_get_text_shadow (theme_node);
+
+ if (shadow_spec)
+ {
+ ClutterActorBox shadow_box;
+
+ st_shadow_get_box (shadow_spec, &alloc_box, &shadow_box);
+ clutter_paint_volume_union_box (volume, &shadow_box);
+ }
+
+ /* Based on ClutterGroup/ClutterBox; include the children's
+ * paint volumes, since they may paint outside our allocation.
+ */
+ for (child = clutter_actor_get_first_child (self);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ {
+ const ClutterPaintVolume *child_volume;
+
+ if (!clutter_actor_is_visible (child))
+ continue;
+
+ child_volume = clutter_actor_get_transformed_paint_volume (child, self);
+ if (!child_volume)
+ return FALSE;
+
+ clutter_paint_volume_union (volume, child_volume);
+ }
+ }
+
+ return TRUE;
+}
+
+static GList *
+st_widget_real_get_focus_chain (StWidget *widget)
+{
+ GList *children, *l, *visible = NULL;
+
+ children = clutter_actor_get_children (CLUTTER_ACTOR (widget));
+
+ for (l = children; l; l = l->next)
+ {
+ if (clutter_actor_is_visible (CLUTTER_ACTOR (l->data)))
+ visible = g_list_prepend (visible, l->data);
+ }
+
+ g_list_free (children);
+
+ return g_list_reverse (visible);
+}
+
+static void
+st_widget_resource_scale_changed (ClutterActor *actor)
+{
+ StWidget *widget = ST_WIDGET (actor);
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ st_theme_node_paint_state_invalidate (&priv->paint_states[i]);
+
+ if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->resource_scale_changed (actor);
+}
+
+static void
+st_widget_class_init (StWidgetClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ gobject_class->set_property = st_widget_set_property;
+ gobject_class->get_property = st_widget_get_property;
+ gobject_class->constructed = st_widget_constructed;
+ gobject_class->dispose = st_widget_dispose;
+ gobject_class->finalize = st_widget_finalize;
+
+ actor_class->get_preferred_width = st_widget_get_preferred_width;
+ actor_class->get_preferred_height = st_widget_get_preferred_height;
+ actor_class->allocate = st_widget_allocate;
+ actor_class->paint = st_widget_paint;
+ actor_class->get_paint_volume = st_widget_get_paint_volume;
+ actor_class->parent_set = st_widget_parent_set;
+ actor_class->map = st_widget_map;
+ actor_class->unmap = st_widget_unmap;
+
+ actor_class->enter_event = st_widget_enter;
+ actor_class->leave_event = st_widget_leave;
+ actor_class->key_focus_in = st_widget_key_focus_in;
+ actor_class->key_focus_out = st_widget_key_focus_out;
+ actor_class->key_press_event = st_widget_key_press_event;
+
+ actor_class->get_accessible = st_widget_get_accessible;
+ actor_class->has_accessible = st_widget_has_accessible;
+
+ actor_class->resource_scale_changed = st_widget_resource_scale_changed;
+
+ klass->style_changed = st_widget_real_style_changed;
+ klass->navigate_focus = st_widget_real_navigate_focus;
+ klass->get_accessible_type = st_widget_accessible_get_type;
+ klass->get_focus_chain = st_widget_real_get_focus_chain;
+
+ /**
+ * StWidget:pseudo-class:
+ *
+ * The pseudo-class of the actor. Typical values include "hover", "active",
+ * "focus".
+ */
+ props[PROP_PSEUDO_CLASS] =
+ g_param_spec_string ("pseudo-class",
+ "Pseudo Class",
+ "Pseudo class for styling",
+ "",
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:style-class:
+ *
+ * The style-class of the actor for use in styling.
+ */
+ props[PROP_STYLE_CLASS] =
+ g_param_spec_string ("style-class",
+ "Style Class",
+ "Style class for styling",
+ "",
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:style:
+ *
+ * Inline style information for the actor as a ';'-separated list of
+ * CSS properties.
+ */
+ props[PROP_STYLE] =
+ g_param_spec_string ("style",
+ "Style",
+ "Inline style string",
+ "",
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:track-hover:
+ *
+ * Determines whether the widget tracks pointer hover state. If
+ * %TRUE (and the widget is visible and reactive), the
+ * #StWidget:hover property and "hover" style pseudo class will be
+ * adjusted automatically as the pointer moves in and out of the
+ * widget.
+ */
+ props[PROP_TRACK_HOVER] =
+ g_param_spec_boolean ("track-hover",
+ "Track hover",
+ "Determines whether the widget tracks hover state",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:hover:
+ *
+ * Whether or not the pointer is currently hovering over the widget. This is
+ * only tracked automatically if #StWidget:track-hover is %TRUE, but you can
+ * adjust it manually in any case.
+ */
+ props[PROP_HOVER] =
+ g_param_spec_boolean ("hover",
+ "Hover",
+ "Whether the pointer is hovering over the widget",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:can-focus:
+ *
+ * Whether or not the widget can be focused via keyboard navigation.
+ */
+ props[PROP_CAN_FOCUS] =
+ g_param_spec_boolean ("can-focus",
+ "Can focus",
+ "Whether the widget can be focused via keyboard navigation",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:label-actor:
+ *
+ * An actor that labels this widget.
+ */
+ props[PROP_LABEL_ACTOR] =
+ g_param_spec_object ("label-actor",
+ "Label",
+ "Label that identifies this widget",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:accessible-role:
+ *
+ * The accessible role of this object
+ */
+ props[PROP_ACCESSIBLE_ROLE] =
+ g_param_spec_enum ("accessible-role",
+ "Accessible Role",
+ "The accessible role of this object",
+ ATK_TYPE_ROLE,
+ ATK_ROLE_INVALID,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:accessible-name:
+ *
+ * Object instance's name for assistive technology access.
+ */
+ props[PROP_ACCESSIBLE_NAME] =
+ g_param_spec_string ("accessible-name",
+ "Accessible name",
+ "Object instance's name for assistive technology access.",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+ /**
+ * StWidget::style-changed:
+ * @widget: the #StWidget
+ *
+ * Emitted when the style information that the widget derives from the
+ * theme changes
+ */
+ signals[STYLE_CHANGED] =
+ g_signal_new ("style-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StWidgetClass, style_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StWidget::popup-menu:
+ * @widget: the #StWidget
+ *
+ * Emitted when the user has requested a context menu (eg, via a keybinding)
+ */
+ signals[POPUP_MENU] =
+ g_signal_new ("popup-menu",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StWidgetClass, popup_menu),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static const gchar *
+find_class_name (const gchar *class_list,
+ const gchar *class_name)
+{
+ gint len = strlen (class_name);
+ const gchar *match;
+
+ if (!class_list)
+ return NULL;
+
+ for (match = strstr (class_list, class_name); match; match = strstr (match + 1, class_name))
+ {
+ if ((match == class_list || g_ascii_isspace (match[-1])) &&
+ (match[len] == '\0' || g_ascii_isspace (match[len])))
+ return match;
+ }
+
+ return NULL;
+}
+
+static gboolean
+set_class_list (gchar **class_list,
+ const gchar *new_class_list)
+{
+ if (g_strcmp0 (*class_list, new_class_list) != 0)
+ {
+ g_free (*class_list);
+ *class_list = g_strdup (new_class_list);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static gboolean
+add_class_name (gchar **class_list,
+ const gchar *class_name)
+{
+ gchar *new_class_list;
+
+ if (*class_list)
+ {
+ if (find_class_name (*class_list, class_name))
+ return FALSE;
+
+ new_class_list = g_strdup_printf ("%s %s", *class_list, class_name);
+ g_free (*class_list);
+ *class_list = new_class_list;
+ }
+ else
+ *class_list = g_strdup (class_name);
+
+ return TRUE;
+}
+
+static gboolean
+remove_class_name (gchar **class_list,
+ const gchar *class_name)
+{
+ const gchar *match, *end;
+ gchar *new_class_list;
+
+ if (!*class_list)
+ return FALSE;
+
+ if (strcmp (*class_list, class_name) == 0)
+ {
+ g_free (*class_list);
+ *class_list = NULL;
+ return TRUE;
+ }
+
+ match = find_class_name (*class_list, class_name);
+ if (!match)
+ return FALSE;
+ end = match + strlen (class_name);
+
+ /* Adjust either match or end to include a space as well.
+ * (One or the other must be possible at this point.)
+ */
+ if (match != *class_list)
+ match--;
+ else
+ end++;
+
+ new_class_list = g_strdup_printf ("%.*s%s", (int)(match - *class_list),
+ *class_list, end);
+ g_free (*class_list);
+ *class_list = new_class_list;
+
+ return TRUE;
+}
+
+/**
+ * st_widget_set_style_class_name:
+ * @actor: a #StWidget
+ * @style_class_list: (nullable): a new style class list string
+ *
+ * Set the style class name list. @style_class_list can either be
+ * %NULL, for no classes, or a space-separated list of style class
+ * names. See also st_widget_add_style_class_name() and
+ * st_widget_remove_style_class_name().
+ */
+void
+st_widget_set_style_class_name (StWidget *actor,
+ const gchar *style_class_list)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (set_class_list (&priv->style_class, style_class_list))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE_CLASS]);
+ }
+}
+
+/**
+ * st_widget_add_style_class_name:
+ * @actor: a #StWidget
+ * @style_class: a style class name string
+ *
+ * Adds @style_class to @actor's style class name list, if it is not
+ * already present.
+ */
+void
+st_widget_add_style_class_name (StWidget *actor,
+ const gchar *style_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (style_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (add_class_name (&priv->style_class, style_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE_CLASS]);
+ }
+}
+
+/**
+ * st_widget_remove_style_class_name:
+ * @actor: a #StWidget
+ * @style_class: a style class name string
+ *
+ * Removes @style_class from @actor's style class name, if it is
+ * present.
+ */
+void
+st_widget_remove_style_class_name (StWidget *actor,
+ const gchar *style_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (style_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (remove_class_name (&priv->style_class, style_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE_CLASS]);
+ }
+}
+
+/**
+ * st_widget_get_style_class_name:
+ * @actor: a #StWidget
+ *
+ * Get the current style class name
+ *
+ * Returns: the class name string. The string is owned by the #StWidget and
+ * should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style_class_name (StWidget *actor)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ return ST_WIDGET_PRIVATE (actor)->style_class;
+}
+
+/**
+ * st_widget_has_style_class_name:
+ * @actor: a #StWidget
+ * @style_class: a style class string
+ *
+ * Tests if @actor's style class list includes @style_class.
+ *
+ * Returns: whether or not @actor's style class list includes
+ * @style_class.
+ */
+gboolean
+st_widget_has_style_class_name (StWidget *actor,
+ const gchar *style_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
+
+ priv = st_widget_get_instance_private (actor);
+
+ return find_class_name (priv->style_class, style_class) != NULL;
+}
+
+/**
+ * st_widget_get_style_pseudo_class:
+ * @actor: a #StWidget
+ *
+ * Get the current style pseudo class list.
+ *
+ * Note that an actor can have multiple pseudo classes; if you just
+ * want to test for the presence of a specific pseudo class, use
+ * st_widget_has_style_pseudo_class().
+ *
+ * Returns: the pseudo class list string. The string is owned by the
+ * #StWidget and should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style_pseudo_class (StWidget *actor)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ return ST_WIDGET_PRIVATE (actor)->pseudo_class;
+}
+
+/**
+ * st_widget_has_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class: a pseudo class string
+ *
+ * Tests if @actor's pseudo class list includes @pseudo_class.
+ *
+ * Returns: whether or not @actor's pseudo class list includes
+ * @pseudo_class.
+ */
+gboolean
+st_widget_has_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
+
+ priv = st_widget_get_instance_private (actor);
+
+ return find_class_name (priv->pseudo_class, pseudo_class) != NULL;
+}
+
+/**
+ * st_widget_set_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class_list: (nullable): a new pseudo class list string
+ *
+ * Set the style pseudo class list. @pseudo_class_list can either be
+ * %NULL, for no classes, or a space-separated list of pseudo class
+ * names. See also st_widget_add_style_pseudo_class() and
+ * st_widget_remove_style_pseudo_class().
+ */
+void
+st_widget_set_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class_list)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (set_class_list (&priv->pseudo_class, pseudo_class_list))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_PSEUDO_CLASS]);
+ }
+}
+
+/**
+ * st_widget_add_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class: a pseudo class string
+ *
+ * Adds @pseudo_class to @actor's pseudo class list, if it is not
+ * already present.
+ */
+void
+st_widget_add_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (pseudo_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (add_class_name (&priv->pseudo_class, pseudo_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_PSEUDO_CLASS]);
+ }
+}
+
+/**
+ * st_widget_remove_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class: a pseudo class string
+ *
+ * Removes @pseudo_class from @actor's pseudo class, if it is present.
+ */
+void
+st_widget_remove_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (pseudo_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (remove_class_name (&priv->pseudo_class, pseudo_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_PSEUDO_CLASS]);
+ }
+}
+
+/**
+ * st_widget_set_style:
+ * @actor: a #StWidget
+ * @style: (nullable): a inline style string, or %NULL
+ *
+ * Set the inline style string for this widget. The inline style string is an
+ * optional ';'-separated list of CSS properties that override the style as
+ * determined from the stylesheets of the current theme.
+ */
+void
+st_widget_set_style (StWidget *actor,
+ const gchar *style)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (g_strcmp0 (style, priv->inline_style))
+ {
+ g_free (priv->inline_style);
+ priv->inline_style = g_strdup (style);
+
+ st_widget_style_changed (actor);
+
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE]);
+ }
+}
+
+/**
+ * st_widget_get_style:
+ * @actor: a #StWidget
+ *
+ * Get the current inline style string. See st_widget_set_style().
+ *
+ * Returns: (transfer none) (nullable): The inline style string, or %NULL. The
+ * string is owned by the #StWidget and should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style (StWidget *actor)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ return ST_WIDGET_PRIVATE (actor)->inline_style;
+}
+
+static void
+st_widget_set_first_visible_child (StWidget *widget,
+ ClutterActor *actor)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->first_visible_child == NULL && actor == NULL)
+ return;
+
+ if (priv->first_visible_child != NULL &&
+ CLUTTER_ACTOR (priv->first_visible_child) == actor)
+ return;
+
+ if (priv->first_visible_child != NULL)
+ {
+ st_widget_remove_style_pseudo_class (priv->first_visible_child, "first-child");
+ g_clear_object (&priv->first_visible_child);
+ }
+
+ if (actor == NULL)
+ return;
+
+ if (ST_IS_WIDGET (actor))
+ {
+ st_widget_add_style_pseudo_class (ST_WIDGET (actor), "first-child");
+ priv->first_visible_child = g_object_ref (ST_WIDGET (actor));
+ }
+}
+
+static void
+st_widget_set_last_visible_child (StWidget *widget,
+ ClutterActor *actor)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->last_visible_child == NULL && actor == NULL)
+ return;
+
+ if (priv->last_visible_child != NULL &&
+ CLUTTER_ACTOR (priv->last_visible_child) == actor)
+ return;
+
+ if (priv->last_visible_child != NULL)
+ {
+ st_widget_remove_style_pseudo_class (priv->last_visible_child, "last-child");
+ g_clear_object (&priv->last_visible_child);
+ }
+
+ if (actor == NULL)
+ return;
+
+ if (ST_IS_WIDGET (actor))
+ {
+ st_widget_add_style_pseudo_class (ST_WIDGET (actor), "last-child");
+ priv->last_visible_child = g_object_ref (ST_WIDGET (actor));
+ }
+}
+
+static void
+st_widget_name_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ st_widget_style_changed (widget);
+}
+
+static void
+st_widget_reactive_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ st_widget_update_insensitive (widget);
+
+ if (priv->track_hover)
+ st_widget_sync_hover(widget);
+}
+
+static ClutterActor *
+find_nearest_visible_backwards (ClutterActor *actor)
+{
+ ClutterActor *prev = actor;
+
+ while (prev != NULL && !clutter_actor_is_visible (prev))
+ prev = clutter_actor_get_previous_sibling (prev);
+ return prev;
+}
+
+static ClutterActor *
+find_nearest_visible_forward (ClutterActor *actor)
+{
+ ClutterActor *next = actor;
+
+ while (next != NULL && !clutter_actor_is_visible (next))
+ next = clutter_actor_get_next_sibling (next);
+ return next;
+}
+
+static gboolean
+st_widget_update_child_styles (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->first_child_dirty)
+ {
+ ClutterActor *first_child;
+
+ priv->first_child_dirty = FALSE;
+
+ first_child = clutter_actor_get_first_child (CLUTTER_ACTOR (widget));
+ st_widget_set_first_visible_child (widget,
+ find_nearest_visible_forward (first_child));
+ }
+
+ if (priv->last_child_dirty)
+ {
+ ClutterActor *last_child;
+
+ priv->last_child_dirty = FALSE;
+
+ last_child = clutter_actor_get_last_child (CLUTTER_ACTOR (widget));
+ st_widget_set_last_visible_child (widget,
+ find_nearest_visible_backwards (last_child));
+ }
+
+ priv->update_child_styles_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+st_widget_queue_child_styles_update (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->update_child_styles_id != 0)
+ return;
+
+ priv->update_child_styles_id = g_idle_add ((GSourceFunc) st_widget_update_child_styles, widget);
+}
+
+static void
+st_widget_visible_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *parent_priv;
+ ClutterActor *actor = CLUTTER_ACTOR (widget);
+ ClutterActor *parent = clutter_actor_get_parent (actor);
+
+ if (parent == NULL || !ST_IS_WIDGET (parent))
+ return;
+
+ parent_priv = st_widget_get_instance_private (ST_WIDGET (parent));
+
+ if (clutter_actor_is_visible (actor))
+ {
+ ClutterActor *before, *after;
+
+ before = clutter_actor_get_previous_sibling (actor);
+ if (find_nearest_visible_backwards (before) == NULL)
+ parent_priv->first_child_dirty = TRUE;
+
+ after = clutter_actor_get_next_sibling (actor);
+ if (find_nearest_visible_forward (after) == NULL)
+ parent_priv->last_child_dirty = TRUE;
+ }
+ else
+ {
+ if (st_widget_has_style_pseudo_class (widget, "first-child"))
+ parent_priv->first_child_dirty = TRUE;
+
+ if (st_widget_has_style_pseudo_class (widget, "last-child"))
+ parent_priv->last_child_dirty = TRUE;
+ }
+
+ if (parent_priv->first_child_dirty || parent_priv->last_child_dirty)
+ st_widget_queue_child_styles_update (ST_WIDGET (parent));
+}
+
+static void
+st_widget_first_child_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ priv->first_child_dirty = TRUE;
+ st_widget_queue_child_styles_update (widget);
+}
+
+static void
+st_widget_last_child_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ priv->last_child_dirty = TRUE;
+ st_widget_queue_child_styles_update (widget);
+}
+
+static void
+st_widget_init (StWidget *actor)
+{
+ StWidgetPrivate *priv;
+ guint i;
+
+ priv = st_widget_get_instance_private (actor);
+ priv->transition_animation = NULL;
+ priv->local_state_set = atk_state_set_new ();
+
+ /* connect style changed */
+ g_signal_connect (actor, "notify::name", G_CALLBACK (st_widget_name_notify), NULL);
+ g_signal_connect (actor, "notify::reactive", G_CALLBACK (st_widget_reactive_notify), NULL);
+
+ g_signal_connect (actor, "notify::visible", G_CALLBACK (st_widget_visible_notify), NULL);
+ g_signal_connect (actor, "notify::first-child", G_CALLBACK (st_widget_first_child_notify), NULL);
+ g_signal_connect (actor, "notify::last-child", G_CALLBACK (st_widget_last_child_notify), NULL);
+ priv->texture_file_changed_id = g_signal_connect (st_texture_cache_get_default (), "texture-file-changed",
+ G_CALLBACK (st_widget_texture_cache_changed), actor);
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ st_theme_node_paint_state_init (&priv->paint_states[i]);
+}
+
+static void
+on_transition_completed (StThemeNodeTransition *transition,
+ StWidget *widget)
+{
+ next_paint_state (widget);
+
+ st_theme_node_paint_state_copy (current_paint_state (widget),
+ st_theme_node_transition_get_new_paint_state (transition));
+
+ st_widget_remove_transition (widget);
+}
+
+static void
+st_widget_recompute_style (StWidget *widget,
+ StThemeNode *old_theme_node)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ StThemeNode *new_theme_node = st_widget_get_theme_node (widget);
+ int transition_duration;
+ StSettings *settings;
+ gboolean paint_equal, geometry_equal = FALSE;
+ gboolean animations_enabled;
+
+ if (new_theme_node == old_theme_node)
+ {
+ priv->is_style_dirty = FALSE;
+ return;
+ }
+
+ _st_theme_node_apply_margins (new_theme_node, CLUTTER_ACTOR (widget));
+
+ if (old_theme_node)
+ geometry_equal = st_theme_node_geometry_equal (old_theme_node, new_theme_node);
+ if (!geometry_equal)
+ clutter_actor_queue_relayout ((ClutterActor *) widget);
+
+ transition_duration = st_theme_node_get_transition_duration (new_theme_node);
+
+ paint_equal = st_theme_node_paint_equal (old_theme_node, new_theme_node);
+
+ settings = st_settings_get ();
+ g_object_get (settings, "enable-animations", &animations_enabled, NULL);
+
+ if (animations_enabled && transition_duration > 0)
+ {
+ if (priv->transition_animation != NULL)
+ {
+ st_theme_node_transition_update (priv->transition_animation,
+ new_theme_node);
+ }
+ else if (old_theme_node && !paint_equal)
+ {
+ /* Since our transitions are only of the painting done by StThemeNode, we
+ * only want to start a transition when what is painted changes; if
+ * other visual aspects like the foreground color of a label change,
+ * we can't animate that anyways.
+ */
+
+ priv->transition_animation =
+ st_theme_node_transition_new (CLUTTER_ACTOR (widget),
+ old_theme_node,
+ new_theme_node,
+ current_paint_state (widget),
+ transition_duration);
+
+ g_signal_connect (priv->transition_animation, "completed",
+ G_CALLBACK (on_transition_completed), widget);
+ g_signal_connect_swapped (priv->transition_animation,
+ "new-frame",
+ G_CALLBACK (clutter_actor_queue_redraw),
+ widget);
+ }
+ }
+ else if (priv->transition_animation)
+ {
+ st_widget_remove_transition (widget);
+ }
+
+ if (!paint_equal)
+ {
+ clutter_actor_invalidate_paint_volume (CLUTTER_ACTOR (widget));
+
+ next_paint_state (widget);
+
+ if (!st_theme_node_paint_equal (new_theme_node, current_paint_state (widget)->node))
+ st_theme_node_paint_state_invalidate (current_paint_state (widget));
+ }
+
+ g_signal_emit (widget, signals[STYLE_CHANGED], 0);
+
+ priv->is_style_dirty = FALSE;
+}
+
+/**
+ * st_widget_ensure_style:
+ * @widget: A #StWidget
+ *
+ * Ensures that @widget has read its style information and propagated any
+ * changes to its children.
+ */
+void
+st_widget_ensure_style (StWidget *widget)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->is_style_dirty)
+ {
+ st_widget_recompute_style (widget, NULL);
+ notify_children_of_style_change (CLUTTER_ACTOR (widget));
+ }
+}
+
+/**
+ * st_widget_set_track_hover:
+ * @widget: A #StWidget
+ * @track_hover: %TRUE if the widget should track the pointer hover state
+ *
+ * Enables hover tracking on the #StWidget.
+ *
+ * If hover tracking is enabled, and the widget is visible and
+ * reactive, then @widget's #StWidget:hover property will be updated
+ * automatically to reflect whether the pointer is in @widget (or one
+ * of its children), and @widget's #StWidget:pseudo-class will have
+ * the "hover" class added and removed from it accordingly.
+ *
+ * Note that currently it is not possible to correctly track the hover
+ * state when another actor has a pointer grab. You can use
+ * st_widget_sync_hover() to update the property manually in this
+ * case.
+ */
+void
+st_widget_set_track_hover (StWidget *widget,
+ gboolean track_hover)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->track_hover != track_hover)
+ {
+ priv->track_hover = track_hover;
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_TRACK_HOVER]);
+
+ if (priv->track_hover)
+ st_widget_sync_hover (widget);
+ else
+ st_widget_set_hover (widget, FALSE);
+ }
+}
+
+/**
+ * st_widget_get_track_hover:
+ * @widget: A #StWidget
+ *
+ * Returns the current value of the #StWidget:track-hover property. See
+ * st_widget_set_track_hover() for more information.
+ *
+ * Returns: current value of track-hover on @widget
+ */
+gboolean
+st_widget_get_track_hover (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ return ST_WIDGET_PRIVATE (widget)->track_hover;
+}
+
+/**
+ * st_widget_set_hover:
+ * @widget: A #StWidget
+ * @hover: whether the pointer is hovering over the widget
+ *
+ * Sets @widget's hover property and adds or removes "hover" from its
+ * pseudo class accordingly.
+ *
+ * If you have set #StWidget:track-hover, you should not need to call
+ * this directly. You can call st_widget_sync_hover() if the hover
+ * state might be out of sync due to another actor's pointer grab.
+ */
+void
+st_widget_set_hover (StWidget *widget,
+ gboolean hover)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->hover != hover)
+ {
+ priv->hover = hover;
+ if (priv->hover)
+ st_widget_add_style_pseudo_class (widget, "hover");
+ else
+ st_widget_remove_style_pseudo_class (widget, "hover");
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_HOVER]);
+ }
+}
+
+/**
+ * st_widget_sync_hover:
+ * @widget: A #StWidget
+ *
+ * Sets @widget's hover state according to the current pointer
+ * position. This can be used to ensure that it is correct after
+ * (or during) a pointer grab.
+ */
+void
+st_widget_sync_hover (StWidget *widget)
+{
+ ClutterInputDevice *pointer;
+ ClutterActor *stage;
+ ClutterActor *pointer_actor;
+ ClutterSeat *seat;
+
+ seat = clutter_backend_get_default_seat (clutter_get_default_backend ());
+ pointer = clutter_seat_get_pointer (seat);
+ stage = clutter_actor_get_stage (CLUTTER_ACTOR (widget));
+ if (!stage)
+ return;
+
+ pointer_actor = clutter_stage_get_device_actor (CLUTTER_STAGE (stage), pointer, NULL);
+ if (pointer_actor && clutter_actor_get_reactive (CLUTTER_ACTOR (widget)))
+ st_widget_set_hover (widget, clutter_actor_contains (CLUTTER_ACTOR (widget), pointer_actor));
+ else
+ st_widget_set_hover (widget, FALSE);
+}
+
+/**
+ * st_widget_get_hover:
+ * @widget: A #StWidget
+ *
+ * If #StWidget:track-hover is set, this returns whether the pointer
+ * is currently over the widget.
+ *
+ * Returns: current value of hover on @widget
+ */
+gboolean
+st_widget_get_hover (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ return ST_WIDGET_PRIVATE (widget)->hover;
+}
+
+/**
+ * st_widget_set_can_focus:
+ * @widget: A #StWidget
+ * @can_focus: %TRUE if the widget can receive keyboard focus
+ * via keyboard navigation
+ *
+ * Marks @widget as being able to receive keyboard focus via
+ * keyboard navigation.
+ */
+void
+st_widget_set_can_focus (StWidget *widget,
+ gboolean can_focus)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->can_focus != can_focus)
+ {
+ priv->can_focus = can_focus;
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_CAN_FOCUS]);
+ }
+}
+
+/**
+ * st_widget_get_can_focus:
+ * @widget: A #StWidget
+ *
+ * Returns the current value of the can-focus property. See
+ * st_widget_set_can_focus() for more information.
+ *
+ * Returns: current value of can-focus on @widget
+ */
+gboolean
+st_widget_get_can_focus (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ return ST_WIDGET_PRIVATE (widget)->can_focus;
+}
+
+/**
+ * st_widget_popup_menu:
+ * @self: A #StWidget
+ *
+ * Asks the widget to pop-up a context menu by emitting #StWidget::popup-menu.
+ */
+void
+st_widget_popup_menu (StWidget *self)
+{
+ g_signal_emit (self, signals[POPUP_MENU], 0);
+}
+
+/* filter @children to contain only only actors that overlap @rbox
+ * when moving in @direction. (Assuming no transformations.)
+ */
+static GList *
+filter_by_position (GList *children,
+ ClutterActorBox *rbox,
+ StDirectionType direction)
+{
+ ClutterActorBox cbox;
+ graphene_point3d_t abs_vertices[4];
+ GList *l, *ret;
+ ClutterActor *child;
+
+ for (l = children, ret = NULL; l; l = l->next)
+ {
+ child = l->data;
+ clutter_actor_get_abs_allocation_vertices (child, abs_vertices);
+ clutter_actor_box_from_vertices (&cbox, abs_vertices);
+
+ /* Filter out children if they are in the wrong direction from
+ * @rbox, or if they don't overlap it. To account for floating-
+ * point imprecision, an actor is "down" (etc.) from an another
+ * actor even if it overlaps it by up to 0.1 pixels.
+ */
+ switch (direction)
+ {
+ case ST_DIR_UP:
+ if (cbox.y2 > rbox->y1 + 0.1)
+ continue;
+ break;
+
+ case ST_DIR_DOWN:
+ if (cbox.y1 < rbox->y2 - 0.1)
+ continue;
+ break;
+
+ case ST_DIR_LEFT:
+ if (cbox.x2 > rbox->x1 + 0.1)
+ continue;
+ break;
+
+ case ST_DIR_RIGHT:
+ if (cbox.x1 < rbox->x2 - 0.1)
+ continue;
+ break;
+
+ case ST_DIR_TAB_BACKWARD:
+ case ST_DIR_TAB_FORWARD:
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ ret = g_list_prepend (ret, child);
+ }
+
+ g_list_free (children);
+ return ret;
+}
+
+
+static void
+get_midpoint (ClutterActorBox *box,
+ int *x,
+ int *y)
+{
+ *x = (box->x1 + box->x2) / 2;
+ *y = (box->y1 + box->y2) / 2;
+}
+
+static double
+get_distance (ClutterActor *actor,
+ ClutterActorBox *bbox)
+{
+ int ax, ay, bx, by, dx, dy;
+ ClutterActorBox abox;
+ graphene_point3d_t abs_vertices[4];
+
+ clutter_actor_get_abs_allocation_vertices (actor, abs_vertices);
+ clutter_actor_box_from_vertices (&abox, abs_vertices);
+
+ get_midpoint (&abox, &ax, &ay);
+ get_midpoint (bbox, &bx, &by);
+ dx = ax - bx;
+ dy = ay - by;
+
+ /* Not the exact distance, but good enough to sort by. */
+ return dx*dx + dy*dy;
+}
+
+static int
+sort_by_distance (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ ClutterActor *actor_a = (ClutterActor *)a;
+ ClutterActor *actor_b = (ClutterActor *)b;
+ ClutterActorBox *box = user_data;
+
+ return get_distance (actor_a, box) - get_distance (actor_b, box);
+}
+
+static gboolean
+st_widget_real_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ ClutterActor *widget_actor, *focus_child;
+ GList *children, *l;
+
+ widget_actor = CLUTTER_ACTOR (widget);
+ if (from == widget_actor)
+ return FALSE;
+
+ /* Figure out if @from is a descendant of @widget, and if so,
+ * set @focus_child to the immediate child of @widget that
+ * contains (or *is*) @from.
+ */
+ focus_child = from;
+ while (focus_child && clutter_actor_get_parent (focus_child) != widget_actor)
+ focus_child = clutter_actor_get_parent (focus_child);
+
+ if (priv->can_focus)
+ {
+ if (!focus_child)
+ {
+ if (clutter_actor_is_mapped (widget_actor))
+ {
+ /* Accept focus from outside */
+ clutter_actor_grab_key_focus (widget_actor);
+ return TRUE;
+ }
+ else
+ {
+ /* Refuse to set focus on hidden actors */
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* Yield focus from within: since @widget itself is
+ * focusable we don't allow the focus to be navigated
+ * within @widget.
+ */
+ return FALSE;
+ }
+ }
+
+ /* See if we can navigate within @focus_child */
+ if (focus_child && ST_IS_WIDGET (focus_child))
+ {
+ if (st_widget_navigate_focus (ST_WIDGET (focus_child), from, direction, FALSE))
+ return TRUE;
+ }
+
+ children = st_widget_get_focus_chain (widget);
+ if (direction == ST_DIR_TAB_FORWARD ||
+ direction == ST_DIR_TAB_BACKWARD)
+ {
+ /* At this point we know that we want to navigate focus to one of
+ * @widget's immediate children; the next one after @focus_child, or the
+ * first one if @focus_child is %NULL. (With "next" and "first" being
+ * determined by @direction.)
+ */
+ if (direction == ST_DIR_TAB_BACKWARD)
+ children = g_list_reverse (children);
+
+ if (focus_child)
+ {
+ /* Remove focus_child and any earlier children */
+ while (children && children->data != focus_child)
+ children = g_list_delete_link (children, children);
+ if (children)
+ children = g_list_delete_link (children, children);
+ }
+ }
+ else /* direction is an arrow key, not tab */
+ {
+ ClutterActorBox sort_box;
+ graphene_point3d_t abs_vertices[4];
+
+ /* Compute the allocation box of the previous focused actor. If there
+ * was no previous focus, use the coordinates of the appropriate edge of
+ * @widget.
+ *
+ * Note that all of this code assumes the actors are not
+ * transformed (or at most, they are all scaled by the same
+ * amount). If @widget or any of its children is rotated, or
+ * any child is inconsistently scaled, then the focus chain will
+ * probably be unpredictable.
+ */
+ if (from)
+ {
+ clutter_actor_get_abs_allocation_vertices (from, abs_vertices);
+ clutter_actor_box_from_vertices (&sort_box, abs_vertices);
+ }
+ else
+ {
+ clutter_actor_get_abs_allocation_vertices (widget_actor, abs_vertices);
+ clutter_actor_box_from_vertices (&sort_box, abs_vertices);
+ switch (direction)
+ {
+ case ST_DIR_UP:
+ sort_box.y1 = sort_box.y2;
+ break;
+ case ST_DIR_DOWN:
+ sort_box.y2 = sort_box.y1;
+ break;
+ case ST_DIR_LEFT:
+ sort_box.x1 = sort_box.x2;
+ break;
+ case ST_DIR_RIGHT:
+ sort_box.x2 = sort_box.x1;
+ break;
+ case ST_DIR_TAB_FORWARD:
+ case ST_DIR_TAB_BACKWARD:
+ default:
+ g_warn_if_reached ();
+ }
+ }
+
+ if (from)
+ children = filter_by_position (children, &sort_box, direction);
+ if (children)
+ children = g_list_sort_with_data (children, sort_by_distance, &sort_box);
+ }
+
+ /* Now try each child in turn */
+ for (l = children; l; l = l->next)
+ {
+ if (ST_IS_WIDGET (l->data))
+ {
+ if (st_widget_navigate_focus (l->data, from, direction, FALSE))
+ {
+ g_list_free (children);
+ return TRUE;
+ }
+ }
+ }
+
+ g_list_free (children);
+ return FALSE;
+}
+
+
+/**
+ * st_widget_navigate_focus:
+ * @widget: the "top level" container
+ * @from: (nullable): the actor that the focus is coming from
+ * @direction: the direction focus is moving in
+ * @wrap_around: whether focus should wrap around
+ *
+ * Tries to update the keyboard focus within @widget in response to a
+ * keyboard event.
+ *
+ * If @from is a descendant of @widget, this attempts to move the
+ * keyboard focus to the next descendant of @widget (in the order
+ * implied by @direction) that has the #StWidget:can-focus property
+ * set. If @from is %NULL, this attempts to focus either @widget
+ * itself, or its first descendant in the order implied by
+ * @direction. If @from is outside of @widget, it behaves as if it was
+ * a descendant if @direction is one of the directional arrows and as
+ * if it was %NULL otherwise.
+ *
+ * If a container type is marked #StWidget:can-focus, the expected
+ * behavior is that it will only take up a single slot on the focus
+ * chain as a whole, rather than allowing navigation between its child
+ * actors (or having a distinction between itself being focused and
+ * one of its children being focused).
+ *
+ * Some widget classes might have slightly different behavior from the
+ * above, where that would make more sense.
+ *
+ * If @wrap_around is %TRUE and @from is a child of @widget, but the
+ * widget has no further children that can accept the focus in the
+ * given direction, then st_widget_navigate_focus() will try a second
+ * time, using a %NULL @from, which should cause it to reset the focus
+ * to the first available widget in the given direction.
+ *
+ * Returns: %TRUE if clutter_actor_grab_key_focus() has been
+ * called on an actor. %FALSE if not.
+ */
+gboolean
+st_widget_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction,
+ gboolean wrap_around)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ if (ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, from, direction))
+ return TRUE;
+ if (wrap_around && from && clutter_actor_contains (CLUTTER_ACTOR (widget), from))
+ return ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, NULL, direction);
+ return FALSE;
+}
+
+static gboolean
+append_actor_text (GString *desc,
+ ClutterActor *actor)
+{
+ if (CLUTTER_IS_TEXT (actor))
+ {
+ g_string_append_printf (desc, " (\"%s\")",
+ clutter_text_get_text (CLUTTER_TEXT (actor)));
+ return TRUE;
+ }
+ else if (ST_IS_LABEL (actor))
+ {
+ g_string_append_printf (desc, " (\"%s\")",
+ st_label_get_text (ST_LABEL (actor)));
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/**
+ * st_describe_actor:
+ * @actor: a #ClutterActor
+ *
+ * Creates a string describing @actor, for use in debugging. This
+ * includes the class name and actor name (if any), plus if @actor
+ * is an #StWidget, its style class and pseudo class names.
+ *
+ * Returns: the debug name.
+ */
+char *
+st_describe_actor (ClutterActor *actor)
+{
+ GString *desc;
+ const char *name;
+ int i;
+
+ if (!actor)
+ return g_strdup ("[null]");
+
+ desc = g_string_new (NULL);
+ g_string_append_printf (desc, "[%p %s", actor,
+ G_OBJECT_TYPE_NAME (actor));
+
+ if (ST_IS_WIDGET (actor))
+ {
+ const char *style_class = st_widget_get_style_class_name (ST_WIDGET (actor));
+ const char *pseudo_class = st_widget_get_style_pseudo_class (ST_WIDGET (actor));
+ char **classes;
+
+ if (style_class)
+ {
+ classes = g_strsplit (style_class, ",", -1);
+ for (i = 0; classes[i]; i++)
+ {
+ g_strchug (classes[i]);
+ g_string_append_printf (desc, ".%s", classes[i]);
+ }
+ g_strfreev (classes);
+ }
+
+ if (pseudo_class)
+ {
+ classes = g_strsplit (pseudo_class, ",", -1);
+ for (i = 0; classes[i]; i++)
+ {
+ g_strchug (classes[i]);
+ g_string_append_printf (desc, ":%s", classes[i]);
+ }
+ g_strfreev (classes);
+ }
+ }
+
+ name = clutter_actor_get_name (actor);
+ if (name)
+ g_string_append_printf (desc, " \"%s\"", name);
+
+ if (!append_actor_text (desc, actor))
+ {
+ GList *children, *l;
+
+ /* Do a limited search of @actor's children looking for a label */
+ children = clutter_actor_get_children (actor);
+ for (l = children, i = 0; l && i < 20; l = l->next, i++)
+ {
+ if (append_actor_text (desc, l->data))
+ break;
+ children = g_list_concat (children, clutter_actor_get_children (l->data));
+ }
+ g_list_free (children);
+ }
+
+ g_string_append_c (desc, ']');
+
+ return g_string_free (desc, FALSE);
+}
+
+/**
+ * st_widget_get_label_actor:
+ * @widget: a #StWidget
+ *
+ * Gets the label that identifies @widget if it is defined
+ *
+ * Returns: (transfer none): the label that identifies the widget
+ */
+ClutterActor *
+st_widget_get_label_actor (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ return ST_WIDGET_PRIVATE (widget)->label_actor;
+}
+
+/**
+ * st_widget_set_label_actor:
+ * @widget: a #StWidget
+ * @label: a #ClutterActor
+ *
+ * Sets @label as the #ClutterActor that identifies (labels)
+ * @widget. @label can be %NULL to indicate that @widget is not
+ * labelled any more
+ */
+
+void
+st_widget_set_label_actor (StWidget *widget,
+ ClutterActor *label)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->label_actor != label)
+ {
+ if (priv->label_actor)
+ g_object_unref (priv->label_actor);
+
+ if (label != NULL)
+ priv->label_actor = g_object_ref (label);
+ else
+ priv->label_actor = NULL;
+
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_LABEL_ACTOR]);
+ }
+}
+
+/**
+ * st_widget_set_accessible_name:
+ * @widget: widget to set the accessible name for
+ * @name: (nullable): a character string to be set as the accessible name
+ *
+ * This method sets @name as the accessible name for @widget.
+ *
+ * Usually you will have no need to set the accessible name for an
+ * object, as usually there is a label for most of the interface
+ * elements. So in general it is better to just use
+ * @st_widget_set_label_actor. This method is only required when you
+ * need to set an accessible name and there is no available label
+ * object.
+ *
+ */
+void
+st_widget_set_accessible_name (StWidget *widget,
+ const gchar *name)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (g_strcmp0 (name, priv->accessible_name) == 0)
+ return;
+
+ if (priv->accessible_name != NULL)
+ g_free (priv->accessible_name);
+
+ priv->accessible_name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_ACCESSIBLE_NAME]);
+}
+
+/**
+ * st_widget_get_accessible_name:
+ * @widget: widget to get the accessible name for
+ *
+ * Gets the accessible name for this widget. See
+ * st_widget_set_accessible_name() for more information.
+ *
+ * Returns: a character string representing the accessible name
+ * of the widget.
+ */
+const gchar *
+st_widget_get_accessible_name (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ return ST_WIDGET_PRIVATE (widget)->accessible_name;
+}
+
+/**
+ * st_widget_set_accessible_role:
+ * @widget: widget to set the accessible role for
+ * @role: The role to use
+ *
+ * This method sets @role as the accessible role for @widget. This
+ * role describes what kind of user interface element @widget is and
+ * is provided so that assistive technologies know how to present
+ * @widget to the user.
+ *
+ * Usually you will have no need to set the accessible role for an
+ * object, as this information is extracted from the context of the
+ * object (ie: a #StButton has by default a push button role). This
+ * method is only required when you need to redefine the role
+ * currently associated with the widget, for instance if it is being
+ * used in an unusual way (ie: a #StButton used as a togglebutton), or
+ * if a generic object is used directly (ie: a container as a menu
+ * item).
+ *
+ * If @role is #ATK_ROLE_INVALID, the role will not be changed
+ * and the accessible's default role will be used instead.
+ */
+void
+st_widget_set_accessible_role (StWidget *widget,
+ AtkRole role)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible_role == role)
+ return;
+
+ priv->accessible_role = role;
+
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_ACCESSIBLE_ROLE]);
+}
+
+
+/**
+ * st_widget_get_accessible_role:
+ * @widget: widget to get the accessible role for
+ *
+ * Gets the #AtkRole for this widget. See
+ * st_widget_set_accessible_role() for more information.
+ *
+ * Returns: accessible #AtkRole for this widget
+ */
+AtkRole
+st_widget_get_accessible_role (StWidget *widget)
+{
+ StWidgetPrivate *priv;
+ AtkRole role = ATK_ROLE_INVALID;
+
+ g_return_val_if_fail (ST_IS_WIDGET (widget), ATK_ROLE_INVALID);
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible_role != ATK_ROLE_INVALID)
+ role = priv->accessible_role;
+ else if (priv->accessible != NULL)
+ role = atk_object_get_role (priv->accessible);
+
+ return role;
+}
+
+static void
+notify_accessible_state_change (StWidget *widget,
+ AtkStateType state,
+ gboolean value)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible != NULL)
+ atk_object_notify_state_change (priv->accessible, state, value);
+}
+
+/**
+ * st_widget_add_accessible_state:
+ * @widget: A #StWidget
+ * @state: #AtkStateType state to add
+ *
+ * This method adds @state as one of the accessible states for
+ * @widget. The list of states of a widget describes the current state
+ * of user interface element @widget and is provided so that assistive
+ * technologies know how to present @widget to the user.
+ *
+ * Usually you will have no need to add accessible states for an
+ * object, as the accessible object can extract most of the states
+ * from the object itself (ie: a #StButton knows when it is pressed).
+ * This method is only required when one cannot extract the
+ * information automatically from the object itself (i.e.: a generic
+ * container used as a toggle menu item will not automatically include
+ * the toggled state).
+ *
+ */
+void
+st_widget_add_accessible_state (StWidget *widget,
+ AtkStateType state)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (atk_state_set_add_state (priv->local_state_set, state))
+ notify_accessible_state_change (widget, state, TRUE);
+}
+
+/**
+ * st_widget_remove_accessible_state:
+ * @widget: A #StWidget
+ * @state: #AtkState state to remove
+ *
+ * This method removes @state as on of the accessible states for
+ * @widget. See st_widget_add_accessible_state() for more information.
+ *
+ */
+void
+st_widget_remove_accessible_state (StWidget *widget,
+ AtkStateType state)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (atk_state_set_remove_state (priv->local_state_set, state))
+ notify_accessible_state_change (widget, state, FALSE);
+}
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+/* GObject */
+
+static void st_widget_accessible_dispose (GObject *gobject);
+
+/* AtkObject */
+static AtkStateSet *st_widget_accessible_ref_state_set (AtkObject *obj);
+static void st_widget_accessible_initialize (AtkObject *obj,
+ gpointer data);
+static AtkRole st_widget_accessible_get_role (AtkObject *obj);
+
+/* Private methods */
+static void on_pseudo_class_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data);
+static void on_can_focus_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data);
+static void on_label_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data);
+static void check_pseudo_class (StWidgetAccessible *self,
+ StWidget *widget);
+static void check_labels (StWidgetAccessible *self,
+ StWidget *widget);
+
+struct _StWidgetAccessiblePrivate
+{
+ /* Cached values (used to avoid extra notifications) */
+ gboolean selected;
+ gboolean checked;
+
+ /* The current_label. Right now there are the proper atk
+ * relationships between this object and the label
+ */
+ AtkObject *current_label;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StWidgetAccessible, st_widget_accessible, CALLY_TYPE_ACTOR)
+
+static gboolean
+st_widget_has_accessible (ClutterActor *actor)
+{
+ StWidget *widget;
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
+
+ widget = ST_WIDGET (actor);
+ priv = st_widget_get_instance_private (widget);
+
+ return priv->accessible != NULL;
+}
+
+static AtkObject *
+st_widget_get_accessible (ClutterActor *actor)
+{
+ StWidget *widget = NULL;
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ widget = ST_WIDGET (actor);
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible == NULL)
+ {
+ priv->accessible =
+ g_object_new (ST_WIDGET_GET_CLASS (widget)->get_accessible_type (),
+ NULL);
+
+ atk_object_initialize (priv->accessible, actor);
+
+ /* AtkGObjectAccessible, which StWidgetAccessible derives from, clears
+ * the back reference to the object in a weak notify for the object;
+ * weak-ref notification, which occurs during g_object_real_dispose(),
+ * is then the optimal time to clear the forward reference. We
+ * can't clear the reference in dispose() before chaining up, since
+ * clutter_actor_dispose() causes notifications to be sent out, which
+ * will result in a new accessible object being created.
+ */
+ g_object_add_weak_pointer (G_OBJECT (actor),
+ (gpointer *)&priv->accessible);
+ }
+
+ return priv->accessible;
+}
+
+/**
+ * st_widget_set_accessible:
+ * @widget: A #StWidget
+ * @accessible: an accessible (#AtkObject)
+ *
+ * This method allows to set a customly created accessible object to
+ * this widget. For example if you define a new subclass of
+ * #StWidgetAccessible at the javascript code.
+ *
+ * NULL is a valid value for @accessible. That contemplates the
+ * hypothetical case of not needing anymore a custom accessible object
+ * for the widget. Next call of st_widget_get_accessible() would
+ * create and return a default accessible.
+ *
+ * It assumes that the call to atk_object_initialize that bound the
+ * gobject with the custom accessible object was already called, so
+ * not a responsibility of this method.
+ *
+ */
+void
+st_widget_set_accessible (StWidget *widget,
+ AtkObject *accessible)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+ g_return_if_fail (accessible == NULL || ATK_IS_GOBJECT_ACCESSIBLE (accessible));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible != accessible)
+ {
+ if (priv->accessible)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (widget),
+ (gpointer *)&priv->accessible);
+ g_object_unref (priv->accessible);
+ priv->accessible = NULL;
+ }
+
+ if (accessible)
+ {
+ priv->accessible = g_object_ref (accessible);
+ /* See note in st_widget_get_accessible() */
+ g_object_add_weak_pointer (G_OBJECT (widget),
+ (gpointer *)&priv->accessible);
+ }
+ else
+ priv->accessible = NULL;
+ }
+}
+
+static const gchar *
+st_widget_accessible_get_name (AtkObject *obj)
+{
+ const gchar* name = NULL;
+
+ g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), NULL);
+
+ name = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_name (obj);
+ if (name == NULL)
+ {
+ StWidget *widget = NULL;
+
+ widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (widget == NULL)
+ name = NULL;
+ else
+ name = st_widget_get_accessible_name (widget);
+ }
+
+ return name;
+}
+
+static void
+st_widget_accessible_class_init (StWidgetAccessibleClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = st_widget_accessible_dispose;
+
+ atk_class->ref_state_set = st_widget_accessible_ref_state_set;
+ atk_class->initialize = st_widget_accessible_initialize;
+ atk_class->get_role = st_widget_accessible_get_role;
+ atk_class->get_name = st_widget_accessible_get_name;
+}
+
+static void
+st_widget_accessible_init (StWidgetAccessible *self)
+{
+ StWidgetAccessiblePrivate *priv = st_widget_accessible_get_instance_private (self);
+
+ self->priv = priv;
+}
+
+static void
+st_widget_accessible_dispose (GObject *gobject)
+{
+ StWidgetAccessible *self = ST_WIDGET_ACCESSIBLE (gobject);
+
+ if (self->priv->current_label)
+ {
+ g_object_unref (self->priv->current_label);
+ self->priv->current_label = NULL;
+ }
+
+ G_OBJECT_CLASS (st_widget_accessible_parent_class)->dispose (gobject);
+}
+
+static void
+on_accessible_name_notify (GObject *gobject,
+ GParamSpec *pspec,
+ AtkObject *accessible)
+{
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+}
+
+static void
+st_widget_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->initialize (obj, data);
+
+ g_signal_connect (data, "notify::pseudo-class",
+ G_CALLBACK (on_pseudo_class_notify),
+ obj);
+
+ g_signal_connect (data, "notify::can-focus",
+ G_CALLBACK (on_can_focus_notify),
+ obj);
+
+ g_signal_connect (data, "notify::label-actor",
+ G_CALLBACK (on_label_notify),
+ obj);
+
+ g_signal_connect (data, "notify::accessible-name",
+ G_CALLBACK (on_accessible_name_notify),
+ obj);
+
+ /* Check the cached selected state and notify the first selection.
+ * Ie: it is required to ensure a first notification when Alt+Tab
+ * popup appears
+ */
+ check_pseudo_class (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
+ check_labels (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
+}
+
+static AtkStateSet *
+st_widget_accessible_ref_state_set (AtkObject *obj)
+{
+ AtkStateSet *result = NULL;
+ AtkStateSet *aux_set = NULL;
+ ClutterActor *actor = NULL;
+ StWidget *widget = NULL;
+ StWidgetPrivate *widget_priv;
+ StWidgetAccessible *self = NULL;
+
+ result = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->ref_state_set (obj);
+
+ actor = CLUTTER_ACTOR (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (actor == NULL) /* State is defunct */
+ return result;
+
+ widget = ST_WIDGET (actor);
+ self = ST_WIDGET_ACCESSIBLE (obj);
+ widget_priv = st_widget_get_instance_private (widget);
+
+ /* priv->selected should be properly updated on the
+ * ATK_STATE_SELECTED notification callbacks
+ */
+ if (self->priv->selected)
+ atk_state_set_add_state (result, ATK_STATE_SELECTED);
+
+ if (self->priv->checked)
+ atk_state_set_add_state (result, ATK_STATE_CHECKED);
+
+ /* On clutter there isn't any tip to know if a actor is focusable or
+ * not, anyone can receive the key_focus. For this reason
+ * cally_actor sets any actor as FOCUSABLE. This is not the case on
+ * St, where we have can_focus. But this means that we need to
+ * remove the state FOCUSABLE if it is not focusable
+ */
+ if (st_widget_get_can_focus (widget))
+ atk_state_set_add_state (result, ATK_STATE_FOCUSABLE);
+ else
+ atk_state_set_remove_state (result, ATK_STATE_FOCUSABLE);
+
+ /* We add the states added externally if required */
+ if (!atk_state_set_is_empty (widget_priv->local_state_set))
+ {
+ aux_set = atk_state_set_or_sets (result, widget_priv->local_state_set);
+
+ g_object_unref (result); /* previous result will not be used */
+ result = aux_set;
+ }
+
+ return result;
+}
+
+static AtkRole
+st_widget_accessible_get_role (AtkObject *obj)
+{
+ StWidget *widget = NULL;
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), ATK_ROLE_INVALID);
+
+ widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (widget == NULL)
+ return ATK_ROLE_INVALID;
+
+ priv = st_widget_get_instance_private (widget);
+ if (priv->accessible_role != ATK_ROLE_INVALID)
+ return priv->accessible_role;
+
+ return ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_role (obj);
+}
+
+static void
+on_pseudo_class_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ check_pseudo_class (ST_WIDGET_ACCESSIBLE (data),
+ ST_WIDGET (gobject));
+}
+
+/*
+ * In some cases the only way to check some states are checking the
+ * pseudo-class. Like if the object is selected (see bug 637830) or if
+ * the object is toggled. This method also notifies a state change if
+ * the value is different to the one cached.
+ *
+ * We also assume that if the object uses that pseudo-class, it makes
+ * sense to notify that state change. It would be possible to refine
+ * that behaviour checking the role (ie: notify CHECKED changes only
+ * for CHECK_BUTTON roles).
+ *
+ * In a ideal world we would have a more standard way to get the
+ * state, like the widget-context (as in the case of
+ * gtktreeview-cells), or something like the property "can-focus". But
+ * for the moment this is enough, and we can update that in the future
+ * if required.
+ */
+static void
+check_pseudo_class (StWidgetAccessible *self,
+ StWidget *widget)
+{
+ gboolean found = FALSE;
+
+ found = st_widget_has_style_pseudo_class (widget,
+ "selected");
+
+ if (found != self->priv->selected)
+ {
+ self->priv->selected = found;
+ atk_object_notify_state_change (ATK_OBJECT (self),
+ ATK_STATE_SELECTED,
+ found);
+ }
+
+ found = st_widget_has_style_pseudo_class (widget,
+ "checked");
+ if (found != self->priv->checked)
+ {
+ self->priv->checked = found;
+ atk_object_notify_state_change (ATK_OBJECT (self),
+ ATK_STATE_CHECKED,
+ found);
+ }
+}
+
+static void
+on_can_focus_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ gboolean can_focus = st_widget_get_can_focus (ST_WIDGET (gobject));
+
+ atk_object_notify_state_change (ATK_OBJECT (data),
+ ATK_STATE_FOCUSABLE, can_focus);
+}
+
+static void
+on_label_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ check_labels (ST_WIDGET_ACCESSIBLE (data), ST_WIDGET (gobject));
+}
+
+static void
+check_labels (StWidgetAccessible *widget_accessible,
+ StWidget *widget)
+{
+ ClutterActor *label = NULL;
+ AtkObject *label_accessible = NULL;
+
+ /* We only call this method at startup, and when the label changes,
+ * so it is fine to remove the previous relationships if we have the
+ * current_label by default
+ */
+ if (widget_accessible->priv->current_label != NULL)
+ {
+ AtkObject *previous_label = widget_accessible->priv->current_label;
+
+ atk_object_remove_relationship (ATK_OBJECT (widget_accessible),
+ ATK_RELATION_LABELLED_BY,
+ previous_label);
+
+ atk_object_remove_relationship (previous_label,
+ ATK_RELATION_LABEL_FOR,
+ ATK_OBJECT (widget_accessible));
+
+ g_object_unref (previous_label);
+ }
+
+ label = st_widget_get_label_actor (widget);
+ if (label == NULL)
+ {
+ widget_accessible->priv->current_label = NULL;
+ }
+ else
+ {
+ label_accessible = clutter_actor_get_accessible (label);
+ widget_accessible->priv->current_label = g_object_ref (label_accessible);
+
+ atk_object_add_relationship (ATK_OBJECT (widget_accessible),
+ ATK_RELATION_LABELLED_BY,
+ label_accessible);
+
+ atk_object_add_relationship (label_accessible,
+ ATK_RELATION_LABEL_FOR,
+ ATK_OBJECT (widget_accessible));
+ }
+}
+
+/**
+ * st_widget_get_focus_chain:
+ * @widget: An #StWidget
+ *
+ * Gets a list of the focusable children of @widget, in "Tab"
+ * order. By default, this returns all visible
+ * (as in clutter_actor_is_visible()) children of @widget.
+ *
+ * Returns: (element-type Clutter.Actor) (transfer container):
+ * @widget's focusable children
+ */
+GList *
+st_widget_get_focus_chain (StWidget *widget)
+{
+ return ST_WIDGET_GET_CLASS (widget)->get_focus_chain (widget);
+}
diff --git a/src/st/st-widget.h b/src/st/st-widget.h
new file mode 100644
index 0000000..f00c987
--- /dev/null
+++ b/src/st/st-widget.h
@@ -0,0 +1,167 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-widget.h: Base class for St actors
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_WIDGET_H__
+#define __ST_WIDGET_H__
+
+#include <clutter/clutter.h>
+#include <st/st-types.h>
+#include <st/st-theme.h>
+#include <st/st-theme-node.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_WIDGET (st_widget_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StWidget, st_widget, ST, WIDGET, ClutterActor)
+
+/**
+ * StDirectionType:
+ * @ST_DIR_TAB_FORWARD: Move forward.
+ * @ST_DIR_TAB_BACKWARD: Move backward.
+ * @ST_DIR_UP: Move up.
+ * @ST_DIR_DOWN: Move down.
+ * @ST_DIR_LEFT: Move left.
+ * @ST_DIR_RIGHT: Move right.
+ *
+ * Enumeration for focus direction.
+ */
+typedef enum
+{
+ ST_DIR_TAB_FORWARD,
+ ST_DIR_TAB_BACKWARD,
+ ST_DIR_UP,
+ ST_DIR_DOWN,
+ ST_DIR_LEFT,
+ ST_DIR_RIGHT,
+} StDirectionType;
+
+typedef struct _StWidgetClass StWidgetClass;
+
+/**
+ * StWidgetClass:
+ *
+ * Base class for stylable actors.
+ */
+struct _StWidgetClass
+{
+ /*< private >*/
+ ClutterActorClass parent_class;
+
+ /* signals */
+ void (* style_changed) (StWidget *self);
+ void (* popup_menu) (StWidget *self);
+
+ /* vfuncs */
+
+ /**
+ * StWidgetClass::navigate_focus:
+ * @self: the "top level" container
+ * @from: (nullable): the actor that the focus is coming from
+ * @direction: the direction focus is moving in
+ */
+ gboolean (* navigate_focus) (StWidget *self,
+ ClutterActor *from,
+ StDirectionType direction);
+ GType (* get_accessible_type) (void);
+
+ GList * (* get_focus_chain) (StWidget *widget);
+};
+
+void st_widget_set_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class_list);
+void st_widget_add_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class);
+void st_widget_remove_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class);
+const gchar * st_widget_get_style_pseudo_class (StWidget *actor);
+gboolean st_widget_has_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class);
+
+void st_widget_set_style_class_name (StWidget *actor,
+ const gchar *style_class_list);
+void st_widget_add_style_class_name (StWidget *actor,
+ const gchar *style_class);
+void st_widget_remove_style_class_name (StWidget *actor,
+ const gchar *style_class);
+const gchar * st_widget_get_style_class_name (StWidget *actor);
+gboolean st_widget_has_style_class_name (StWidget *actor,
+ const gchar *style_class);
+
+void st_widget_set_style (StWidget *actor,
+ const gchar *style);
+const gchar * st_widget_get_style (StWidget *actor);
+void st_widget_set_track_hover (StWidget *widget,
+ gboolean track_hover);
+gboolean st_widget_get_track_hover (StWidget *widget);
+void st_widget_set_hover (StWidget *widget,
+ gboolean hover);
+void st_widget_sync_hover (StWidget *widget);
+gboolean st_widget_get_hover (StWidget *widget);
+void st_widget_popup_menu (StWidget *self);
+
+void st_widget_ensure_style (StWidget *widget);
+
+void st_widget_set_can_focus (StWidget *widget,
+ gboolean can_focus);
+gboolean st_widget_get_can_focus (StWidget *widget);
+gboolean st_widget_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction,
+ gboolean wrap_around);
+
+ClutterActor * st_widget_get_label_actor (StWidget *widget);
+void st_widget_set_label_actor (StWidget *widget,
+ ClutterActor *label);
+
+/* Only to be used by sub-classes of StWidget */
+void st_widget_style_changed (StWidget *widget);
+StThemeNode * st_widget_get_theme_node (StWidget *widget);
+StThemeNode * st_widget_peek_theme_node (StWidget *widget);
+
+GList * st_widget_get_focus_chain (StWidget *widget);
+void st_widget_paint_background (StWidget *widget,
+ ClutterPaintContext *paint_context);
+
+/* debug methods */
+char *st_describe_actor (ClutterActor *actor);
+
+/* accessibility methods */
+void st_widget_set_accessible_role (StWidget *widget,
+ AtkRole role);
+AtkRole st_widget_get_accessible_role (StWidget *widget);
+void st_widget_add_accessible_state (StWidget *widget,
+ AtkStateType state);
+void st_widget_remove_accessible_state (StWidget *widget,
+ AtkStateType state);
+void st_widget_set_accessible_name (StWidget *widget,
+ const gchar *name);
+const gchar * st_widget_get_accessible_name (StWidget *widget);
+void st_widget_set_accessible (StWidget *widget,
+ AtkObject *accessible);
+G_END_DECLS
+
+#endif /* __ST_WIDGET_H__ */
diff --git a/src/st/st.h.in b/src/st/st.h.in
new file mode 100644
index 0000000..825c820
--- /dev/null
+++ b/src/st/st.h.in
@@ -0,0 +1,3 @@
+#define ST_H_INSIDE 1
+@includes@
+#undef ST_H_INSIDE
diff --git a/src/st/test-theme.c b/src/st/test-theme.c
new file mode 100644
index 0000000..3fcbd99
--- /dev/null
+++ b/src/st/test-theme.c
@@ -0,0 +1,637 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * test-theme.c: test program for CSS styling code
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope 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 License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <clutter/clutter.h>
+#include "st-theme.h"
+#include "st-theme-context.h"
+#include "st-label.h"
+#include "st-button.h"
+#include <math.h>
+#include <string.h>
+#include <meta-test/meta-context-test.h>
+#include <meta/meta-backend.h>
+
+static ClutterActor *stage;
+static StThemeNode *root;
+static StThemeNode *group1;
+static StThemeNode *text1;
+static StThemeNode *text2;
+static StThemeNode *group2;
+static StThemeNode *text3;
+static StThemeNode *text4;
+static StThemeNode *group3;
+static StThemeNode *group4;
+static StThemeNode *group5;
+static StThemeNode *group6;
+static StThemeNode *button;
+static gboolean fail;
+
+static const char *test;
+
+static void
+assert_font (StThemeNode *node,
+ const char *node_description,
+ const char *expected)
+{
+ char *value = pango_font_description_to_string (st_theme_node_get_font (node));
+
+ if (strcmp (expected, value) != 0)
+ {
+ g_print ("%s: %s.font: expected: %s, got: %s\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+
+ g_free (value);
+}
+
+static void
+assert_font_features (StThemeNode *node,
+ const char *node_description,
+ const char *expected)
+{
+ char *value = st_theme_node_get_font_features (node);
+
+ if (g_strcmp0 (expected, value) != 0)
+ {
+ g_print ("%s: %s.font-feature-settings: expected: %s, got: %s\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+
+ if (value)
+ g_free (value);
+}
+
+static char *
+text_decoration_to_string (StTextDecoration decoration)
+{
+ GString *result = g_string_new (NULL);
+
+ if (decoration & ST_TEXT_DECORATION_UNDERLINE)
+ g_string_append(result, " underline");
+ if (decoration & ST_TEXT_DECORATION_OVERLINE)
+ g_string_append(result, " overline");
+ if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
+ g_string_append(result, " line_through");
+ if (decoration & ST_TEXT_DECORATION_BLINK)
+ g_string_append(result, " blink");
+
+ if (result->len > 0)
+ g_string_erase (result, 0, 1);
+ else
+ g_string_append(result, "none");
+
+ return g_string_free (result, FALSE);
+}
+
+static void
+assert_text_decoration (StThemeNode *node,
+ const char *node_description,
+ StTextDecoration expected)
+{
+ StTextDecoration value = st_theme_node_get_text_decoration (node);
+ if (expected != value)
+ {
+ char *es = text_decoration_to_string (expected);
+ char *vs = text_decoration_to_string (value);
+
+ g_print ("%s: %s.text-decoration: expected: %s, got: %s\n",
+ test, node_description, es, vs);
+ fail = TRUE;
+
+ g_free (es);
+ g_free (vs);
+ }
+}
+
+static void
+assert_foreground_color (StThemeNode *node,
+ const char *node_description,
+ guint32 expected)
+{
+ ClutterColor color;
+ guint32 value;
+
+ st_theme_node_get_foreground_color (node, &color);
+ value = clutter_color_to_pixel (&color);
+
+ if (expected != value)
+ {
+ g_print ("%s: %s.color: expected: #%08x, got: #%08x\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+}
+
+static void
+assert_background_color (StThemeNode *node,
+ const char *node_description,
+ guint32 expected)
+{
+ ClutterColor color;
+ guint32 value;
+
+ st_theme_node_get_background_color (node, &color);
+ value = clutter_color_to_pixel (&color);
+
+ if (expected != value)
+ {
+ g_print ("%s: %s.background-color: expected: #%08x, got: #%08x\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+}
+
+static const char *
+side_to_string (StSide side)
+{
+ switch (side)
+ {
+ case ST_SIDE_TOP:
+ return "top";
+ case ST_SIDE_RIGHT:
+ return "right";
+ case ST_SIDE_BOTTOM:
+ return "bottom";
+ case ST_SIDE_LEFT:
+ return "left";
+ default:
+ return "<unknown>";
+ }
+}
+
+static void
+assert_border_color (StThemeNode *node,
+ const char *node_description,
+ StSide side,
+ guint32 expected)
+{
+ ClutterColor color;
+ guint32 value;
+
+ st_theme_node_get_border_color (node, side, &color);
+ value = clutter_color_to_pixel (&color);
+
+ if (expected != value)
+ {
+ g_print ("%s: %s.border-%s-color: expected: #%08x, got: #%08x\n",
+ test, node_description, side_to_string (side), expected, value);
+ fail = TRUE;
+ }
+}
+
+static void
+assert_background_image (StThemeNode *node,
+ const char *node_description,
+ const char *expected)
+{
+ GFile *value = st_theme_node_get_background_image (node);
+ GFile *expected_file;
+
+ if (expected != NULL && value != NULL)
+ {
+ expected_file = g_file_new_for_path (expected);
+
+ if (!g_file_equal (expected_file, value))
+ {
+ char *uri = g_file_get_uri (expected_file);
+ g_print ("%s: %s.background-image: expected: %s, got: %s\n",
+ test, node_description, expected, uri);
+ fail = TRUE;
+ g_free (uri);
+ }
+ }
+}
+
+#define LENGTH_EPSILON 0.001
+
+static void
+assert_length (const char *node_description,
+ const char *property_description,
+ double expected,
+ double value)
+{
+ if (fabs (expected - value) > LENGTH_EPSILON)
+ {
+ g_print ("%s %s.%s: expected: %3f, got: %3f\n",
+ test, node_description, property_description, expected, value);
+ fail = TRUE;
+ }
+}
+
+static void
+test_defaults (void)
+{
+ test = "defaults";
+ /* font comes from context */
+ assert_font (root, "stage", "sans-serif 12");
+ /* black is the default foreground color */
+ assert_foreground_color (root, "stage", 0x00000ff);
+}
+
+static void
+test_lengths (void)
+{
+ test = "lengths";
+ /* 12pt == 16px at 96dpi */
+ assert_length ("group1", "padding-top", 16.,
+ st_theme_node_get_padding (group1, ST_SIDE_TOP));
+ /* 12px == 12px */
+ assert_length ("group1", "padding-right", 12.,
+ st_theme_node_get_padding (group1, ST_SIDE_RIGHT));
+ /* 2em == 32px (with a 12pt font) */
+ assert_length ("group1", "padding-bottom", 32.,
+ st_theme_node_get_padding (group1, ST_SIDE_BOTTOM));
+ /* 1in == 72pt == 96px, at 96dpi */
+ assert_length ("group1", "padding-left", 96.,
+ st_theme_node_get_padding (group1, ST_SIDE_LEFT));
+
+ /* 12pt == 16px at 96dpi */
+ assert_length ("group1", "margin-top", 16.,
+ st_theme_node_get_margin (group1, ST_SIDE_TOP));
+ /* 12px == 12px */
+ assert_length ("group1", "margin-right", 12.,
+ st_theme_node_get_margin (group1, ST_SIDE_RIGHT));
+ /* 2em == 32px (with a 12pt font) */
+ assert_length ("group1", "margin-bottom", 32.,
+ st_theme_node_get_margin (group1, ST_SIDE_BOTTOM));
+ /* 1in == 72pt == 96px, at 96dpi */
+ assert_length ("group1", "margin-left", 96.,
+ st_theme_node_get_margin (group1, ST_SIDE_LEFT));
+}
+
+static void
+test_classes (void)
+{
+ test = "classes";
+ /* .special-text class overrides size and style;
+ * the StBin.special-text selector doesn't match */
+ assert_font (text1, "text1", "sans-serif Italic 32px");
+}
+
+static void
+test_type_inheritance (void)
+{
+ test = "type_inheritance";
+ /* From StBin element selector */
+ assert_length ("button", "padding-top", 10.,
+ st_theme_node_get_padding (button, ST_SIDE_TOP));
+ /* From StButton element selector */
+ assert_length ("button", "padding-right", 20.,
+ st_theme_node_get_padding (button, ST_SIDE_RIGHT));
+}
+
+static void
+test_adjacent_selector (void)
+{
+ test = "adjacent_selector";
+ /* #group1 > #text1 matches text1 */
+ assert_foreground_color (text1, "text1", 0x00ff00ff);
+ /* stage > #text2 doesn't match text2 */
+ assert_foreground_color (text2, "text2", 0x000000ff);
+}
+
+static void
+test_padding (void)
+{
+ test = "padding";
+ /* Test that a 4-sided padding property assigns the right paddings to
+ * all sides */
+ assert_length ("group2", "padding-top", 1.,
+ st_theme_node_get_padding (group2, ST_SIDE_TOP));
+ assert_length ("group2", "padding-right", 2.,
+ st_theme_node_get_padding (group2, ST_SIDE_RIGHT));
+ assert_length ("group2", "padding-bottom", 3.,
+ st_theme_node_get_padding (group2, ST_SIDE_BOTTOM));
+ assert_length ("group2", "padding-left", 4.,
+ st_theme_node_get_padding (group2, ST_SIDE_LEFT));
+}
+
+static void
+test_margin (void)
+{
+ test = "margin";
+ /* Test that a 4-sided margin property assigns the right margin to
+ * all sides */
+ assert_length ("group2", "margin-top", 1.,
+ st_theme_node_get_margin (group2, ST_SIDE_TOP));
+ assert_length ("group2", "margin-right", 2.,
+ st_theme_node_get_margin (group2, ST_SIDE_RIGHT));
+ assert_length ("group2", "margin-bottom", 3.,
+ st_theme_node_get_margin (group2, ST_SIDE_BOTTOM));
+ assert_length ("group2", "margin-left", 4.,
+ st_theme_node_get_margin (group2, ST_SIDE_LEFT));
+
+ /* Test that a 3-sided margin property assigns the right margin to
+ * all sides */
+ assert_length ("group4", "margin-top", 1.,
+ st_theme_node_get_margin (group4, ST_SIDE_TOP));
+ assert_length ("group4", "margin-right", 2.,
+ st_theme_node_get_margin (group4, ST_SIDE_RIGHT));
+ assert_length ("group4", "margin-bottom", 3.,
+ st_theme_node_get_margin (group4, ST_SIDE_BOTTOM));
+ assert_length ("group4", "margin-left", 2.,
+ st_theme_node_get_margin (group4, ST_SIDE_LEFT));
+
+ /* Test that a 2-sided margin property assigns the right margin to
+ * all sides */
+ assert_length ("group5", "margin-top", 1.,
+ st_theme_node_get_margin (group5, ST_SIDE_TOP));
+ assert_length ("group5", "margin-right", 2.,
+ st_theme_node_get_margin (group5, ST_SIDE_RIGHT));
+ assert_length ("group5", "margin-bottom", 1.,
+ st_theme_node_get_margin (group5, ST_SIDE_BOTTOM));
+ assert_length ("group5", "margin-left", 2.,
+ st_theme_node_get_margin (group5, ST_SIDE_LEFT));
+
+ /* Test that all sides have a margin of 0 when not specified */
+ assert_length ("group6", "margin-top", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_TOP));
+ assert_length ("group6", "margin-right", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_RIGHT));
+ assert_length ("group6", "margin-bottom", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_BOTTOM));
+ assert_length ("group6", "margin-left", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_LEFT));
+}
+
+static void
+test_border (void)
+{
+ test = "border";
+
+ /* group2 is defined as having a thin black border along the top three
+ * sides with rounded joins, then a square-joined green border at the
+ * bottom
+ */
+
+ assert_length ("group2", "border-top-width", 2.,
+ st_theme_node_get_border_width (group2, ST_SIDE_TOP));
+ assert_length ("group2", "border-right-width", 2.,
+ st_theme_node_get_border_width (group2, ST_SIDE_RIGHT));
+ assert_length ("group2", "border-bottom-width", 5.,
+ st_theme_node_get_border_width (group2, ST_SIDE_BOTTOM));
+ assert_length ("group2", "border-left-width", 2.,
+ st_theme_node_get_border_width (group2, ST_SIDE_LEFT));
+
+ assert_border_color (group2, "group2", ST_SIDE_TOP, 0x000000ff);
+ assert_border_color (group2, "group2", ST_SIDE_RIGHT, 0x000000ff);
+ assert_border_color (group2, "group2", ST_SIDE_BOTTOM, 0x0000ffff);
+ assert_border_color (group2, "group2", ST_SIDE_LEFT, 0x000000ff);
+
+ assert_length ("group2", "border-radius-topleft", 10.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_TOPLEFT));
+ assert_length ("group2", "border-radius-topright", 10.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_TOPRIGHT));
+ assert_length ("group2", "border-radius-bottomright", 0.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_BOTTOMRIGHT));
+ assert_length ("group2", "border-radius-bottomleft", 0.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_BOTTOMLEFT));
+}
+
+static void
+test_background (void)
+{
+ test = "background";
+ /* group1 has a background: shortcut property setting color and image */
+ assert_background_color (group1, "group1", 0xff0000ff);
+ assert_background_image (group1, "group1", "some-background.png");
+ /* text1 inherits the background image but not the color */
+ assert_background_color (text1, "text1", 0x00000000);
+ assert_background_image (text1, "text1", "some-background.png");
+ /* text2 inherits both, but then background: none overrides both */
+ assert_background_color (text2, "text2", 0x00000000);
+ assert_background_image (text2, "text2", NULL);
+ /* background-image property */
+ assert_background_image (group2, "group2", "other-background.png");
+}
+
+static void
+test_font (void)
+{
+ test = "font";
+ /* font specified with font: */
+ assert_font (group2, "group2", "serif Italic 12px");
+ /* text3 inherits and overrides individually properties */
+ assert_font (text3, "text3", "serif Bold Oblique Small-Caps 24px");
+}
+
+static void
+test_font_features (void)
+{
+ test = "font_features";
+ /* group1 has font-feature-settings: "tnum" */
+ assert_font_features (group1, "group1", "\"tnum\"");
+ /* text2 should inherit from group1 */
+ assert_font_features (text2, "text2", "\"tnum\"");
+ /* group2 has font-feature-settings: "tnum", "zero" */
+ assert_font_features (group2, "group2", "\"tnum\", \"zero\"");
+ /* text3 should inherit from group2 using the inherit keyword */
+ assert_font_features (text3, "text3", "\"tnum\", \"zero\"");
+ /* text4 has font-feature-settings: normal */
+ assert_font_features (text4, "text4", NULL);
+}
+
+static void
+test_pseudo_class (void)
+{
+ StWidget *label;
+ StThemeNode *labelNode;
+
+ test = "pseudo_class";
+ /* text4 has :visited and :hover pseudo-classes, so should pick up both of these */
+ assert_foreground_color (text4, "text4", 0x888888ff);
+ assert_text_decoration (text4, "text4", ST_TEXT_DECORATION_UNDERLINE);
+ /* :hover pseudo-class matches, but class doesn't match */
+ assert_text_decoration (group3, "group3", 0);
+
+ /* Test the StWidget add/remove pseudo_class interfaces */
+ label = st_label_new ("foo");
+ clutter_actor_add_child (stage, CLUTTER_ACTOR (label));
+
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_add_style_pseudo_class (label, "visited");
+ g_assert (st_widget_has_style_pseudo_class (label, "visited"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x888888ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_add_style_pseudo_class (label, "hover");
+ g_assert (st_widget_has_style_pseudo_class (label, "hover"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x888888ff);
+ assert_text_decoration (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_remove_style_pseudo_class (label, "visited");
+ g_assert (!st_widget_has_style_pseudo_class (label, "visited"));
+ g_assert (st_widget_has_style_pseudo_class (label, "hover"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_add_style_pseudo_class (label, "boxed");
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
+ assert_length ("label", "border-width", 1.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_remove_style_pseudo_class (label, "hover");
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 1.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_remove_style_pseudo_class (label, "boxed");
+ g_assert (!st_widget_has_style_pseudo_class (label, "boxed"));
+ g_assert (st_widget_has_style_pseudo_class (label, "insensitive"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (label), TRUE);
+ g_assert (st_widget_get_style_pseudo_class (label) == NULL);
+}
+
+static void
+test_inline_style (void)
+{
+ test = "inline_style";
+ /* These properties come from the inline-style specified when creating the node */
+ assert_foreground_color (text3, "text3", 0x00000ffff);
+ assert_length ("text3", "padding-bottom", 12.,
+ st_theme_node_get_padding (text3, ST_SIDE_BOTTOM));
+}
+
+int
+main (int argc, char **argv)
+{
+ MetaContext *context;
+ g_autoptr (GError) error = NULL;
+ MetaBackend *backend;
+ StTheme *theme;
+ StThemeContext *theme_context;
+ PangoFontDescription *font_desc;
+ GFile *file;
+ g_autofree char *cwd = NULL;
+
+ gtk_init (&argc, &argv);
+
+ /* meta_init() cds to $HOME */
+ cwd = g_get_current_dir ();
+
+ context = meta_create_test_context (META_CONTEXT_TEST_TYPE_NESTED,
+ META_CONTEXT_TEST_FLAG_NONE);
+ if (!meta_context_configure (context, &argc, &argv, &error))
+ g_error ("Failed to configure: %s", error->message);
+
+ if (!meta_context_setup (context, &error))
+ g_error ("Failed to setup: %s", error->message);
+
+ if (chdir (cwd) < 0)
+ g_error ("chdir('%s') failed: %s", cwd, g_strerror (errno));
+
+ /* Make sure our assumptions about resolution are correct */
+ g_object_set (clutter_settings_get_default (), "font-dpi", -1, NULL);
+
+ file = g_file_new_for_path ("test-theme.css");
+ theme = st_theme_new (file, NULL, NULL);
+ g_object_unref (file);
+
+ backend = meta_get_backend ();
+ stage = meta_backend_get_stage (backend);
+ theme_context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
+ st_theme_context_set_theme (theme_context, theme);
+
+ font_desc = pango_font_description_from_string ("sans-serif 12");
+ st_theme_context_set_font (theme_context, font_desc);
+ pango_font_description_free (font_desc);
+
+ root = st_theme_context_get_root_node (theme_context);
+ group1 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group1", NULL, NULL, NULL);
+ text1 = st_theme_node_new (theme_context, group1, NULL,
+ CLUTTER_TYPE_TEXT, "text1", "special-text", NULL, NULL);
+ text2 = st_theme_node_new (theme_context, group1, NULL,
+ CLUTTER_TYPE_TEXT, "text2", NULL, NULL, NULL);
+ group2 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group2", NULL, NULL, NULL);
+ group4 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group4", NULL, NULL, NULL);
+ group5 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group5", NULL, NULL, NULL);
+ group6 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group6", NULL, NULL, NULL);
+ text3 = st_theme_node_new (theme_context, group2, NULL,
+ CLUTTER_TYPE_TEXT, "text3", NULL, NULL,
+ "color: #0000ff; padding-bottom: 12px;");
+ text4 = st_theme_node_new (theme_context, group2, NULL,
+ CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover", NULL);
+ group3 = st_theme_node_new (theme_context, group2, NULL,
+ CLUTTER_TYPE_ACTOR, "group3", NULL, "hover", NULL);
+ button = st_theme_node_new (theme_context, root, NULL,
+ ST_TYPE_BUTTON, "button", NULL, NULL, NULL);
+
+ test_defaults ();
+ test_lengths ();
+ test_classes ();
+ test_type_inheritance ();
+ test_adjacent_selector ();
+ test_padding ();
+ test_margin ();
+ test_border ();
+ test_background ();
+ test_font ();
+ test_font_features ();
+ test_pseudo_class ();
+ test_inline_style ();
+
+ g_object_unref (button);
+ g_object_unref (group1);
+ g_object_unref (group2);
+ g_object_unref (group3);
+ g_object_unref (group4);
+ g_object_unref (group5);
+ g_object_unref (group6);
+ g_object_unref (text1);
+ g_object_unref (text2);
+ g_object_unref (text3);
+ g_object_unref (text4);
+ g_object_unref (theme);
+
+ g_object_unref (context);
+
+ return fail ? 1 : 0;
+}
diff --git a/src/st/test-theme.css b/src/st/test-theme.css
new file mode 100644
index 0000000..d180255
--- /dev/null
+++ b/src/st/test-theme.css
@@ -0,0 +1,107 @@
+stage {
+}
+
+#group1 {
+ padding: 12pt;
+ padding-right: 12px;
+ padding-bottom: 2em;
+ padding-left: 1in;
+
+ margin: 12pt;
+ margin-right: 12px;
+ margin-bottom: 2em;
+ margin-left: 1in;
+
+ background: #ff0000 url('some-background.png');
+
+ font-feature-settings: "tnum";
+}
+
+#text1 {
+ background-image: inherit;
+}
+
+.special-text {
+ font-size: 24pt;
+ font-style: italic;
+}
+
+StBin.special-text {
+ font-weight: bold;
+}
+
+#text2 {
+ background: inherit;
+ background: none; /* also overrides the color */
+}
+
+#group2 {
+ font: italic 12px serif;
+ font-feature-settings: "tnum", "zero";
+}
+
+#text3 {
+ font-variant: small-caps;
+ font-weight: bold;
+ font-style: oblique;
+ font-size: 200%;
+ font-feature-settings: "pnum";
+}
+
+#text4 {
+ font-feature-settings: normal;
+}
+
+StBin {
+ padding: 10px;
+}
+
+StButton {
+ padding-right: 20px;
+}
+
+#group1 > #text1 {
+ color: #00ff00;
+}
+
+stage > #text2 {
+ color: #ff0000;
+}
+
+#group2 > #text3 {
+ font-feature-settings: inherit;
+}
+
+#group2 {
+ background-image: url('other-background.png');
+ padding: 1px 2px 3px 4px;
+ margin: 1px 2px 3px 4px;
+
+ border: 2px solid #000000;
+ border-bottom: 5px solid #0000ff;
+ border-radius: 10px 10px 0px 0px;
+}
+
+ClutterText:hover, StLabel:hover {
+ text-decoration: underline;
+}
+
+ClutterText:visited, StLabel:visited {
+ color: #888888;
+}
+
+StLabel:boxed {
+ border: 1px;
+}
+
+#group4 {
+ margin: 1px 2px 3px;
+}
+
+#group5 {
+ margin: 1px 2px;
+}
+
+#group6 {
+ padding: 5px;
+}