diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:54:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:54:43 +0000 |
commit | e4283f6d48b98e764b988b43bbc86b9d52e6ec94 (patch) | |
tree | c8f7f7a6c2f5faa2942d27cefc6fd46cca492656 /src/st | |
parent | Initial commit. (diff) | |
download | gnome-shell-upstream.tar.xz gnome-shell-upstream.zip |
Adding upstream version 43.9.upstream/43.9upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/st')
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, + ¶m); + + 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, ¤t_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, ¤t_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, ¤t_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, ¤t_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; +} |