Import stylesheet code from hippo-canvas
Import: HippoCanvasTheme => StTheme HippoCanvasThemeImage => StThemeImage HippoCanvasStyle => StThemeNode StThemeContext is a new class managing the theme for a stage and global properties like resolution. test-theme.c is a newly written test program to do verification of the style matching and property handling rules. Various changes are made in the import: - Comprehensive reindentation - guint32 pixels replaced with ClutterColor - General pseudo-class support added - Old-fashioned (non-bordered) background image support added, though with no support for repeat, etc. - Bug fixes for problems revealed by test program https://bugzilla.gnome.org/show_bug.cgi?id=595990
This commit is contained in:
parent
6b95864076
commit
276d9a9302
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,6 +38,7 @@ src/gnomeshell-taskpanel
|
||||
src/gnome-shell
|
||||
src/test-recorder
|
||||
src/test-recorder.ogg
|
||||
src/test-theme
|
||||
stamp-h1
|
||||
tests/run-test.sh
|
||||
xmldocs.make
|
||||
|
@ -60,6 +60,7 @@ PKG_CHECK_MODULES(TIDY, clutter-1.0)
|
||||
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libccss-1 >= 0.3.1 clutter-imcontext-0.1)
|
||||
PKG_CHECK_MODULES(BIG, clutter-1.0 gtk+-2.0 librsvg-2.0)
|
||||
PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
|
||||
PKG_CHECK_MODULES(TOOLKIT, clutter-1.0 libcroco-0.6)
|
||||
PKG_CHECK_MODULES(TRAY, gtk+-2.0)
|
||||
|
||||
MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin
|
||||
|
@ -73,7 +73,6 @@ st_source_h = \
|
||||
st/st-clipboard.h \
|
||||
st/st-entry.h \
|
||||
st/st-label.h \
|
||||
st/st-private.h \
|
||||
st/st-stylable.h \
|
||||
st/st-style.h \
|
||||
st/st-scrollable.h \
|
||||
@ -82,11 +81,19 @@ st_source_h = \
|
||||
st/st-subtexture.h \
|
||||
st/st-texture-cache.h \
|
||||
st/st-texture-frame.h \
|
||||
st/st-theme.h \
|
||||
st/st-theme-context.h \
|
||||
st/st-theme-image.h \
|
||||
st/st-theme-node.h \
|
||||
st/st-tooltip.h \
|
||||
st/st-types.h \
|
||||
st/st-widget.h \
|
||||
$(NULL)
|
||||
|
||||
st_source_private_h = \
|
||||
st/st-private.h \
|
||||
st/st-theme-private.h
|
||||
|
||||
# please, keep this sorted alphabetically
|
||||
st_source_c = \
|
||||
st/st-adjustment.c \
|
||||
@ -106,6 +113,10 @@ st_source_c = \
|
||||
st/st-subtexture.c \
|
||||
st/st-texture-cache.c \
|
||||
st/st-texture-frame.c \
|
||||
st/st-theme.c \
|
||||
st/st-theme-context.c \
|
||||
st/st-theme-image.c \
|
||||
st/st-theme-node.c \
|
||||
st/st-tooltip.c \
|
||||
st/st-widget.c \
|
||||
$(NULL)
|
||||
@ -115,8 +126,16 @@ noinst_LTLIBRARIES += libst-1.0.la
|
||||
libst_1_0_la_LIBADD = $(ST_LIBS)
|
||||
libst_1_0_la_SOURCES = \
|
||||
$(st_source_c) \
|
||||
$(st_source_private_c) \
|
||||
$(st_source_h) \
|
||||
$(st_built_sources) \
|
||||
$(NULL)
|
||||
libst_1_0_la_CPPFLAGS = $(st_cflags)
|
||||
libst_1_0_la_LDFLAGS = $(LDADD)
|
||||
|
||||
noinst_PROGRAMS += test-theme
|
||||
|
||||
test_theme_CPPFLAGS = $(st_cflags)
|
||||
test_theme_LDADD = libst-1.0.la
|
||||
|
||||
test_theme_SOURCES = st/test-theme.c
|
||||
|
@ -4,6 +4,7 @@ CLEANFILES =
|
||||
EXTRA_DIST =
|
||||
libexec_PROGRAMS =
|
||||
noinst_LTLIBRARIES =
|
||||
noinst_PROGRAMS =
|
||||
|
||||
.AUTOPARALLEL:
|
||||
|
||||
@ -95,7 +96,7 @@ libgnome_shell_la_SOURCES = \
|
||||
shell-wm.c \
|
||||
shell-wm.h
|
||||
|
||||
non_gir_sources = \
|
||||
non_gir_sources = \
|
||||
shell-embedded-window-private.h
|
||||
|
||||
shell_recorder_sources = \
|
||||
@ -113,7 +114,7 @@ if BUILD_RECORDER
|
||||
libgnome_shell_la_SOURCES += $(shell_recorder_sources)
|
||||
non_gir_sources += $(shell_recorder_non_gir_sources)
|
||||
|
||||
noinst_PROGRAMS = test-recorder
|
||||
noinst_PROGRAMS += test-recorder
|
||||
|
||||
test_recorder_CPPFLAGS = $(TEST_SHELL_RECORDER_CFLAGS)
|
||||
test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
|
||||
|
287
src/st/st-theme-context.c
Normal file
287
src/st/st-theme-context.c
Normal file
@ -0,0 +1,287 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "st-theme.h"
|
||||
#include "st-theme-context.h"
|
||||
|
||||
struct _StThemeContext {
|
||||
GObject parent;
|
||||
|
||||
double resolution;
|
||||
PangoFontDescription *font;
|
||||
StThemeNode *root_node;
|
||||
StTheme *theme;
|
||||
};
|
||||
|
||||
struct _StThemeContextClass {
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
#define DEFAULT_RESOLUTION 96.
|
||||
#define DEFAULT_FONT "sans-serif 10"
|
||||
|
||||
enum
|
||||
{
|
||||
CHANGED,
|
||||
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
static guint signals[LAST_SIGNAL] = { 0, };
|
||||
|
||||
G_DEFINE_TYPE (StThemeContext, st_theme_context, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
st_theme_context_finalize (GObject *object)
|
||||
{
|
||||
StThemeContext *context = ST_THEME_CONTEXT (object);
|
||||
|
||||
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->finalize = st_theme_context_finalize;
|
||||
|
||||
signals[CHANGED] =
|
||||
g_signal_new ("changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0, /* no default handler slot */
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__VOID,
|
||||
G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
st_theme_context_init (StThemeContext *context)
|
||||
{
|
||||
context->resolution = DEFAULT_RESOLUTION;
|
||||
context->font = pango_font_description_from_string (DEFAULT_FONT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
StThemeContext *
|
||||
st_theme_context_new (void)
|
||||
{
|
||||
StThemeContext *context;
|
||||
|
||||
context = g_object_new (ST_TYPE_THEME_CONTEXT, NULL);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
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_signal_emit (context, signals[CHANGED], 0);
|
||||
|
||||
if (old_root)
|
||||
g_object_unref (old_root);
|
||||
}
|
||||
|
||||
/**
|
||||
* st_theme_context_get_for_stage:
|
||||
* @stage: a #ClutterStage
|
||||
*
|
||||
* Gets a singleton theme context associated with the stage.
|
||||
*
|
||||
* Return value: (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
|
||||
*
|
||||
* 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 explicitely 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_object_unref (context->theme);
|
||||
|
||||
context->theme = theme;
|
||||
|
||||
if (context->theme)
|
||||
g_object_ref (context->theme);
|
||||
|
||||
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()
|
||||
*
|
||||
* Return value: (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_resolution:
|
||||
* @context: a #StThemeContext
|
||||
* @resolution: resolution of the context (number of pixels in an "inch")
|
||||
*
|
||||
* Sets the resolution of the theme context. This is the scale factor
|
||||
* used to convert between points and the length units pt, in, and cm.
|
||||
* This does not necessarily need to correspond to the actual number
|
||||
* resolution of the device. A value of 72. means that points and
|
||||
* pixels are identical. The default value is 96.
|
||||
*/
|
||||
void
|
||||
st_theme_context_set_resolution (StThemeContext *context,
|
||||
double resolution)
|
||||
{
|
||||
g_return_if_fail (ST_IS_THEME_CONTEXT (context));
|
||||
|
||||
if (resolution == context->resolution)
|
||||
return;
|
||||
|
||||
context->resolution = resolution;
|
||||
st_theme_context_changed (context);
|
||||
}
|
||||
|
||||
/**
|
||||
* st_theme_context_set_resolution:
|
||||
* @context: a #StThemeContext
|
||||
*
|
||||
* Gets the current resolution of the theme context.
|
||||
* See st_theme_context_set_resolution().
|
||||
*
|
||||
* Return value: the resolution (in dots-per-"inch")
|
||||
*/
|
||||
double
|
||||
st_theme_context_get_resolution (StThemeContext *context)
|
||||
{
|
||||
g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), DEFAULT_RESOLUTION);
|
||||
|
||||
return context->resolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 overriden, 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().
|
||||
*
|
||||
* Return value: 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.
|
||||
*
|
||||
* Return value: (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);
|
||||
|
||||
return context->root_node;
|
||||
}
|
50
src/st/st-theme-context.h
Normal file
50
src/st/st-theme-context.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
#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:StThemeContext
|
||||
* @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().
|
||||
*/
|
||||
|
||||
typedef struct _StThemeContextClass StThemeContextClass;
|
||||
|
||||
#define ST_TYPE_THEME_CONTEXT (st_theme_context_get_type ())
|
||||
#define ST_THEME_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME_CONTEXT, StThemeContext))
|
||||
#define ST_THEME_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_THEME_CONTEXT, StThemeContextClass))
|
||||
#define ST_IS_THEME_CONTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME_CONTEXT))
|
||||
#define ST_IS_THEME_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_THEME_CONTEXT))
|
||||
#define ST_THEME_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_THEME_CONTEXT, StThemeContextClass))
|
||||
|
||||
GType st_theme_context_get_type (void) G_GNUC_CONST;
|
||||
|
||||
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_resolution (StThemeContext *context,
|
||||
gdouble resolution);
|
||||
double st_theme_context_get_resolution (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);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __ST_THEME_CONTEXT_H__ */
|
92
src/st/st-theme-image.c
Normal file
92
src/st/st-theme-image.c
Normal file
@ -0,0 +1,92 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "st-theme-image.h"
|
||||
|
||||
struct _StThemeImage {
|
||||
GObject parent;
|
||||
|
||||
char *filename;
|
||||
int border_top;
|
||||
int border_right;
|
||||
int border_bottom;
|
||||
int border_left;
|
||||
};
|
||||
|
||||
struct _StThemeImageClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (StThemeImage, st_theme_image, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
st_theme_image_finalize (GObject *object)
|
||||
{
|
||||
StThemeImage *image = ST_THEME_IMAGE (object);
|
||||
|
||||
g_free (image->filename);
|
||||
|
||||
G_OBJECT_CLASS (st_theme_image_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
st_theme_image_class_init (StThemeImageClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = st_theme_image_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
st_theme_image_init (StThemeImage *image)
|
||||
{
|
||||
}
|
||||
|
||||
StThemeImage *
|
||||
st_theme_image_new (const char *filename,
|
||||
int border_top,
|
||||
int border_right,
|
||||
int border_bottom,
|
||||
int border_left)
|
||||
{
|
||||
StThemeImage *image;
|
||||
|
||||
image = g_object_new (ST_TYPE_THEME_IMAGE, NULL);
|
||||
|
||||
image->filename = g_strdup (filename);
|
||||
image->border_top = border_top;
|
||||
image->border_right = border_right;
|
||||
image->border_bottom = border_bottom;
|
||||
image->border_left = border_left;
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
const char *
|
||||
st_theme_image_get_filename (StThemeImage *image)
|
||||
{
|
||||
g_return_val_if_fail (ST_IS_THEME_IMAGE (image), NULL);
|
||||
|
||||
return image->filename;
|
||||
}
|
||||
|
||||
void
|
||||
st_theme_image_get_borders (StThemeImage *image,
|
||||
int *border_top,
|
||||
int *border_right,
|
||||
int *border_bottom,
|
||||
int *border_left)
|
||||
{
|
||||
g_return_if_fail (ST_IS_THEME_IMAGE (image));
|
||||
|
||||
if (border_top)
|
||||
*border_top = image->border_top;
|
||||
if (border_right)
|
||||
*border_right = image->border_right;
|
||||
if (border_bottom)
|
||||
*border_bottom = image->border_bottom;
|
||||
if (border_left)
|
||||
*border_left = image->border_left;
|
||||
}
|
38
src/st/st-theme-image.h
Normal file
38
src/st/st-theme-image.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
#ifndef __ST_THEME_IMAGE_H__
|
||||
#define __ST_THEME_IMAGE_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* A StThemeImage encapsulates an image with specified unscaled borders on each edge.
|
||||
*/
|
||||
typedef struct _StThemeImage StThemeImage;
|
||||
typedef struct _StThemeImageClass StThemeImageClass;
|
||||
|
||||
#define ST_TYPE_THEME_IMAGE (st_theme_image_get_type ())
|
||||
#define ST_THEME_IMAGE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME_IMAGE, StThemeImage))
|
||||
#define ST_THEME_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_THEME_IMAGE, StThemeImageClass))
|
||||
#define ST_IS_THEME_IMAGE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME_IMAGE))
|
||||
#define ST_IS_THEME_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_THEME_IMAGE))
|
||||
#define ST_THEME_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_THEME_IMAGE, StThemeImageClass))
|
||||
|
||||
GType st_theme_image_get_type (void) G_GNUC_CONST;
|
||||
|
||||
StThemeImage *st_theme_image_new (const char *filename,
|
||||
int border_top,
|
||||
int border_right,
|
||||
int border_bottom,
|
||||
int border_left);
|
||||
|
||||
const char *st_theme_image_get_filename (StThemeImage *image);
|
||||
void st_theme_image_get_borders (StThemeImage *image,
|
||||
int *border_top,
|
||||
int *border_right,
|
||||
int *border_bottom,
|
||||
int *border_left);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __ST_THEME_IMAGE_H__ */
|
1813
src/st/st-theme-node.c
Normal file
1813
src/st/st-theme-node.c
Normal file
File diff suppressed because it is too large
Load Diff
125
src/st/st-theme-node.h
Normal file
125
src/st/st-theme-node.h
Normal file
@ -0,0 +1,125 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
#ifndef __ST_THEME_NODE_H__
|
||||
#define __ST_THEME_NODE_H__
|
||||
|
||||
#include <clutter/clutter.h>
|
||||
#include "st-theme-image.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* SECTION:StThemeNode
|
||||
* @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.
|
||||
*/
|
||||
|
||||
typedef struct _StTheme StTheme;
|
||||
typedef struct _StThemeContext StThemeContext;
|
||||
|
||||
typedef struct _StThemeNode StThemeNode;
|
||||
typedef struct _StThemeNodeClass StThemeNodeClass;
|
||||
|
||||
#define ST_TYPE_THEME_NODE (st_theme_node_get_type ())
|
||||
#define ST_THEME_NODE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME_NODE, StThemeNode))
|
||||
#define ST_THEME_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_THEME_NODE, StThemeNodeClass))
|
||||
#define ST_IS_THEME_NODE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME_NODE))
|
||||
#define ST_IS_THEME_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_THEME_NODE))
|
||||
#define ST_THEME_NODE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_THEME_NODE, StThemeNodeClass))
|
||||
|
||||
typedef enum {
|
||||
ST_SIDE_LEFT,
|
||||
ST_SIDE_RIGHT,
|
||||
ST_SIDE_TOP,
|
||||
ST_SIDE_BOTTOM
|
||||
} StSide;
|
||||
|
||||
/* These are the CSS values; that doesn't mean we have to implement blink... */
|
||||
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;
|
||||
|
||||
GType st_theme_node_get_type (void) G_GNUC_CONST;
|
||||
|
||||
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);
|
||||
|
||||
StThemeNode *st_theme_node_get_parent (StThemeNode *node);
|
||||
|
||||
StTheme *st_theme_node_get_theme (StThemeNode *node);
|
||||
|
||||
GType st_theme_node_get_element_type (StThemeNode *node);
|
||||
const char *st_theme_node_get_element_id (StThemeNode *node);
|
||||
const char *st_theme_node_get_element_class (StThemeNode *node);
|
||||
const char *st_theme_node_get_pseudo_class (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_get_color (StThemeNode *node,
|
||||
const char *property_name,
|
||||
gboolean inherit,
|
||||
ClutterColor *color);
|
||||
|
||||
gboolean st_theme_node_get_double (StThemeNode *node,
|
||||
const char *property_name,
|
||||
gboolean inherit,
|
||||
double *value);
|
||||
|
||||
gboolean st_theme_node_get_length (StThemeNode *node,
|
||||
const char *property_name,
|
||||
gboolean inherit,
|
||||
gdouble *length);
|
||||
|
||||
/* 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);
|
||||
|
||||
const char *st_theme_node_get_background_image (StThemeNode *node);
|
||||
|
||||
double st_theme_node_get_border_width (StThemeNode *node,
|
||||
StSide side);
|
||||
void st_theme_node_get_border_color (StThemeNode *node,
|
||||
StSide side,
|
||||
ClutterColor *color);
|
||||
double st_theme_node_get_padding (StThemeNode *node,
|
||||
StSide side);
|
||||
|
||||
StTextDecoration st_theme_node_get_text_decoration (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);
|
||||
|
||||
/* This is the getter for -st-background-image, which is different from
|
||||
* background-image in having provisions for unscaled borders.
|
||||
*/
|
||||
StThemeImage *st_theme_node_get_background_theme_image (StThemeNode *node);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __ST_THEME_NODE_H__ */
|
22
src/st/st-theme-private.h
Normal file
22
src/st/st-theme-private.h
Normal file
@ -0,0 +1,22 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
#ifndef __ST_THEME_PRIVATE_H__
|
||||
#define __ST_THEME_PRIVATE_H__
|
||||
|
||||
#include <libcroco/libcroco.h>
|
||||
#include "st-theme.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void _st_theme_get_matched_properties (StTheme *theme,
|
||||
StThemeNode *node,
|
||||
CRDeclaration ***properties,
|
||||
int *n_properties);
|
||||
|
||||
/* Resolve an URL from the stylesheet to a filename */
|
||||
char *_st_theme_resolve_url (StTheme *theme,
|
||||
CRStyleSheet *base_stylesheet,
|
||||
const char *url);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __ST_THEME_PRIVATE_H__ */
|
971
src/st/st-theme.c
Normal file
971
src/st/st-theme.c
Normal file
@ -0,0 +1,971 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
|
||||
/* 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
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* Copyright (C) 2003-2004 Dodji Seketeli. All Rights Reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "st-theme-node.h"
|
||||
#include "st-theme-private.h"
|
||||
|
||||
static GObject *st_theme_constructor (GType type,
|
||||
guint n_construct_properties,
|
||||
GObjectConstructParam *construct_properties);
|
||||
|
||||
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;
|
||||
|
||||
char *application_stylesheet;
|
||||
char *default_stylesheet;
|
||||
char *theme_stylesheet;
|
||||
|
||||
GHashTable *stylesheets_by_filename;
|
||||
GHashTable *filenames_by_stylesheet;
|
||||
|
||||
CRCascade *cascade;
|
||||
};
|
||||
|
||||
struct _StThemeClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_APPLICATION_STYLESHEET,
|
||||
PROP_THEME_STYLESHEET,
|
||||
PROP_DEFAULT_STYLESHEET
|
||||
};
|
||||
|
||||
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 void
|
||||
st_theme_init (StTheme *theme)
|
||||
{
|
||||
theme->stylesheets_by_filename = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, (GDestroyNotify)cr_stylesheet_unref);
|
||||
theme->filenames_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->constructor = st_theme_constructor;
|
||||
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_string ("application-stylesheet",
|
||||
"Application Stylesheet",
|
||||
"Stylesheet with application-specific styling",
|
||||
NULL,
|
||||
G_PARAM_READABLE | G_PARAM_WRITABLE | 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_string ("theme-stylesheet",
|
||||
"Theme Stylesheet",
|
||||
"Stylesheet with theme-specific styling",
|
||||
NULL,
|
||||
G_PARAM_READABLE | G_PARAM_WRITABLE | 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_string ("default-stylesheet",
|
||||
"Default Stylesheet",
|
||||
"Stylesheet with global default styling",
|
||||
NULL,
|
||||
G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
|
||||
|
||||
}
|
||||
|
||||
static CRStyleSheet *
|
||||
parse_stylesheet (const char *filename)
|
||||
{
|
||||
enum CRStatus status;
|
||||
CRStyleSheet *stylesheet;
|
||||
|
||||
if (filename == NULL)
|
||||
return NULL;
|
||||
|
||||
status = cr_om_parser_simply_parse_file ((const guchar *) filename,
|
||||
CR_UTF_8,
|
||||
&stylesheet);
|
||||
|
||||
if (status != CR_OK)
|
||||
{
|
||||
g_warning ("Error parsing stylesheet '%s'", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stylesheet;
|
||||
}
|
||||
|
||||
static void
|
||||
insert_stylesheet (StTheme *theme,
|
||||
const char *filename,
|
||||
CRStyleSheet *stylesheet)
|
||||
{
|
||||
char *filename_copy;
|
||||
|
||||
if (stylesheet == NULL)
|
||||
return;
|
||||
|
||||
filename_copy = g_strdup(filename);
|
||||
cr_stylesheet_ref (stylesheet);
|
||||
|
||||
g_hash_table_insert (theme->stylesheets_by_filename, filename_copy, stylesheet);
|
||||
g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
|
||||
}
|
||||
|
||||
static GObject *
|
||||
st_theme_constructor (GType type,
|
||||
guint n_construct_properties,
|
||||
GObjectConstructParam *construct_properties)
|
||||
{
|
||||
GObject *object;
|
||||
StTheme *theme;
|
||||
CRStyleSheet *application_stylesheet;
|
||||
CRStyleSheet *theme_stylesheet;
|
||||
CRStyleSheet *default_stylesheet;
|
||||
|
||||
object = (*G_OBJECT_CLASS (st_theme_parent_class)->constructor) (type,
|
||||
n_construct_properties,
|
||||
construct_properties);
|
||||
theme = ST_THEME (object);
|
||||
|
||||
application_stylesheet = parse_stylesheet (theme->application_stylesheet);
|
||||
theme_stylesheet = parse_stylesheet (theme->theme_stylesheet);
|
||||
default_stylesheet = parse_stylesheet (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);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static void
|
||||
st_theme_finalize (GObject * object)
|
||||
{
|
||||
StTheme *theme = ST_THEME (object);
|
||||
|
||||
g_hash_table_destroy (theme->stylesheets_by_filename);
|
||||
g_hash_table_destroy (theme->filenames_by_stylesheet);
|
||||
|
||||
g_free (theme->application_stylesheet);
|
||||
g_free (theme->theme_stylesheet);
|
||||
g_free (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:
|
||||
{
|
||||
const char *path = g_value_get_string (value);
|
||||
|
||||
if (path != theme->application_stylesheet)
|
||||
{
|
||||
g_free (theme->application_stylesheet);
|
||||
theme->application_stylesheet = g_strdup (path);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PROP_THEME_STYLESHEET:
|
||||
{
|
||||
const char *path = g_value_get_string (value);
|
||||
|
||||
if (path != theme->theme_stylesheet)
|
||||
{
|
||||
g_free (theme->theme_stylesheet);
|
||||
theme->theme_stylesheet = g_strdup (path);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PROP_DEFAULT_STYLESHEET:
|
||||
{
|
||||
const char *path = g_value_get_string (value);
|
||||
|
||||
if (path != theme->default_stylesheet)
|
||||
{
|
||||
g_free (theme->default_stylesheet);
|
||||
theme->default_stylesheet = g_strdup (path);
|
||||
}
|
||||
|
||||
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_string (value, theme->application_stylesheet);
|
||||
break;
|
||||
case PROP_THEME_STYLESHEET:
|
||||
g_value_set_string (value, theme->theme_stylesheet);
|
||||
break;
|
||||
case PROP_DEFAULT_STYLESHEET:
|
||||
g_value_set_string (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
|
||||
*
|
||||
* Return value: the newly created theme object
|
||||
**/
|
||||
StTheme *
|
||||
st_theme_new (const char *application_stylesheet,
|
||||
const char *theme_stylesheet,
|
||||
const char *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,
|
||||
const char *list)
|
||||
{
|
||||
const char *cur;
|
||||
|
||||
for (cur = list; *cur;)
|
||||
{
|
||||
while (*cur && cr_utils_is_white_space (*cur))
|
||||
cur++;
|
||||
|
||||
if (strncmp (cur, stryng->str, stryng->len) == 0)
|
||||
{
|
||||
cur += stryng->len;
|
||||
if ((!*cur) || cr_utils_is_white_space (*cur))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* skip to next whitespace character */
|
||||
while (*cur && !cr_utils_is_white_space (*cur))
|
||||
cur++;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pseudo_class_add_sel_matches_style (StTheme *a_this,
|
||||
CRAdditionalSel *a_add_sel,
|
||||
StThemeNode *a_node)
|
||||
{
|
||||
const char *node_pseudo_class;
|
||||
|
||||
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_class = st_theme_node_get_pseudo_class (a_node);
|
||||
|
||||
if (node_pseudo_class == NULL)
|
||||
return FALSE;
|
||||
|
||||
return string_in_list (a_add_sel->content.pseudo->name->stryng, node_pseudo_class);
|
||||
}
|
||||
|
||||
/**
|
||||
*@param a_add_sel the class additional selector to consider.
|
||||
*@param a_node the style node to consider.
|
||||
*@return 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)
|
||||
{
|
||||
const char *element_class;
|
||||
|
||||
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_class = st_theme_node_get_element_class (a_node);
|
||||
if (element_class == NULL)
|
||||
return FALSE;
|
||||
|
||||
return string_in_list (a_add_sel->content.class_name->stryng, element_class);
|
||||
}
|
||||
|
||||
/**
|
||||
*@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;
|
||||
}
|
||||
|
||||
/**
|
||||
*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;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
/*
|
||||
*initialyze 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_MEDIA_RULE_STMT:
|
||||
if (cur_stmt->kind.media_rule
|
||||
&& cur_stmt->kind.media_rule->rulesets
|
||||
&& cur_stmt->kind.media_rule->rulesets->kind.ruleset
|
||||
&& cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
|
||||
{
|
||||
sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
|
||||
}
|
||||
break;
|
||||
|
||||
case AT_IMPORT_RULE_STMT:
|
||||
{
|
||||
CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
|
||||
|
||||
if (import_rule->sheet == NULL)
|
||||
{
|
||||
char *filename = NULL;
|
||||
|
||||
if (import_rule->url->stryng && import_rule->url->stryng->str)
|
||||
filename = _st_theme_resolve_url (a_this,
|
||||
a_nodesheet,
|
||||
import_rule->url->stryng->str);
|
||||
|
||||
if (filename)
|
||||
import_rule->sheet = parse_stylesheet (filename);
|
||||
|
||||
if (import_rule->sheet)
|
||||
{
|
||||
insert_stylesheet (a_this, filename, 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 (filename)
|
||||
g_free (filename);
|
||||
}
|
||||
|
||||
if (import_rule->sheet != (CRStyleSheet *) - 1)
|
||||
{
|
||||
add_matched_properties (a_this, import_rule->sheet,
|
||||
a_node, props);
|
||||
}
|
||||
}
|
||||
break;
|
||||
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 overriden.
|
||||
*/
|
||||
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_AUTHOR_IMPORTANT (ORIGIN_AUTHOR + 1)
|
||||
#define ORIGIN_USER_IMPORTANT (ORIGIN_AUTHOR + 2)
|
||||
|
||||
static inline int
|
||||
get_origin (const CRDeclaration * decl)
|
||||
{
|
||||
enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
|
||||
|
||||
if (decl->important)
|
||||
{
|
||||
if (origin == ORIGIN_AUTHOR)
|
||||
return ORIGIN_AUTHOR_IMPORTANT;
|
||||
else if (origin == ORIGIN_USER)
|
||||
return ORIGIN_USER_IMPORTANT;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void
|
||||
_st_theme_get_matched_properties (StTheme *theme,
|
||||
StThemeNode *node,
|
||||
CRDeclaration ***properties,
|
||||
int *n_properties)
|
||||
{
|
||||
enum CRStyleOrigin origin = 0;
|
||||
CRStyleSheet *sheet = NULL;
|
||||
GPtrArray *props = g_ptr_array_new ();
|
||||
|
||||
g_return_if_fail (ST_IS_THEME (theme));
|
||||
g_return_if_fail (ST_IS_THEME_NODE (node));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* We count on a stable sort here so that later declarations come
|
||||
* after earlier declarations */
|
||||
g_ptr_array_sort (props, compare_declarations);
|
||||
|
||||
*n_properties = props->len;
|
||||
*properties = (CRDeclaration **) g_ptr_array_free (props, FALSE);
|
||||
}
|
||||
|
||||
/* Resolve an url from an url() reference in a stylesheet into an absolute
|
||||
* local filename, if possible. The resolution here is distinctly lame and
|
||||
* will fail on many examples.
|
||||
*/
|
||||
char *
|
||||
_st_theme_resolve_url (StTheme *theme,
|
||||
CRStyleSheet *base_stylesheet,
|
||||
const char *url)
|
||||
{
|
||||
const char *base_filename = NULL;
|
||||
char *dirname;
|
||||
char *filename;
|
||||
|
||||
/* Handle absolute file:/ URLs */
|
||||
if (g_str_has_prefix (url, "file:") ||
|
||||
g_str_has_prefix (url, "File:") ||
|
||||
g_str_has_prefix (url, "FILE:"))
|
||||
{
|
||||
GError *error = NULL;
|
||||
char *filename;
|
||||
|
||||
filename = g_filename_from_uri (url, NULL, &error);
|
||||
if (filename == NULL)
|
||||
{
|
||||
g_warning ("%s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Guard against http:/ URLs */
|
||||
|
||||
if (g_str_has_prefix (url, "http:") ||
|
||||
g_str_has_prefix (url, "Http:") ||
|
||||
g_str_has_prefix (url, "HTTP:"))
|
||||
{
|
||||
g_warning ("Http URL '%s' in theme stylesheet is not supported", url);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Assume anything else is a relative URL, and "resolve" it
|
||||
*/
|
||||
if (url[0] == '/')
|
||||
return g_strdup (url);
|
||||
|
||||
base_filename = g_hash_table_lookup (theme->filenames_by_stylesheet, base_stylesheet);
|
||||
|
||||
if (base_filename == NULL)
|
||||
{
|
||||
g_warning ("Can't get base to resolve url '%s'", url);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dirname = g_path_get_dirname (base_filename);
|
||||
filename = g_build_filename (dirname, url, NULL);
|
||||
g_free (dirname);
|
||||
|
||||
return filename;
|
||||
}
|
38
src/st/st-theme.h
Normal file
38
src/st/st-theme.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
#ifndef __ST_THEME_H__
|
||||
#define __ST_THEME_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "st-theme-node.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* SECTION:StTheme
|
||||
* @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() or applied to a subtree
|
||||
* of actors using st_widget_set_theme().
|
||||
*/
|
||||
|
||||
typedef struct _StThemeClass StThemeClass;
|
||||
|
||||
#define ST_TYPE_THEME (st_theme_get_type ())
|
||||
#define ST_THEME(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), ST_TYPE_THEME, StTheme))
|
||||
#define ST_THEME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_THEME, StThemeClass))
|
||||
#define ST_IS_THEME(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), ST_TYPE_THEME))
|
||||
#define ST_IS_THEME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_THEME))
|
||||
#define ST_THEME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_THEME, StThemeClass))
|
||||
|
||||
GType st_theme_get_type (void) G_GNUC_CONST;
|
||||
|
||||
StTheme *st_theme_new (const char *application_stylesheet,
|
||||
const char *theme_stylesheet,
|
||||
const char *default_stylesheet);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __ST_THEME_H__ */
|
292
src/st/test-theme.c
Normal file
292
src/st/test-theme.c
Normal file
@ -0,0 +1,292 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
|
||||
#include <clutter/clutter.h>
|
||||
#include "st-theme.h"
|
||||
#include "st-theme-context.h"
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
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 *cairo_texture;
|
||||
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 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;
|
||||
st_theme_node_get_foreground_color (node, &color);
|
||||
guint32 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;
|
||||
st_theme_node_get_background_color (node, &color);
|
||||
guint32 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 void
|
||||
assert_background_image (StThemeNode *node,
|
||||
const char *node_description,
|
||||
const char *expected)
|
||||
{
|
||||
const char *value = st_theme_node_get_background_image (node);
|
||||
if (expected == NULL)
|
||||
expected = "(null)";
|
||||
if (value == NULL)
|
||||
value = "(null)";
|
||||
|
||||
if (strcmp (expected, value) != 0)
|
||||
{
|
||||
g_print ("%s: %s.background-image: expected: %s, got: %s\n",
|
||||
test, node_description, expected, value);
|
||||
fail = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
#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));
|
||||
}
|
||||
|
||||
static void
|
||||
test_classes (void)
|
||||
{
|
||||
test = "classes";
|
||||
/* .special-text class overrides size and style;
|
||||
* the ClutterTexture.special-text selector doesn't match */
|
||||
assert_font (text1, "text1", "sans-serif Italic 32px");
|
||||
}
|
||||
|
||||
static void
|
||||
test_type_inheritance (void)
|
||||
{
|
||||
test = "type_inheritance";
|
||||
/* From ClutterTexture element selector */
|
||||
assert_length ("cairoTexture", "padding-top", 10.,
|
||||
st_theme_node_get_padding (cairo_texture, ST_SIDE_TOP));
|
||||
/* From ClutterCairoTexture element selector */
|
||||
assert_length ("cairoTexture", "padding-right", 20.,
|
||||
st_theme_node_get_padding (cairo_texture, 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_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", "st/some-background.png");
|
||||
/* text1 inherits the background image but not the color */
|
||||
assert_background_color (text1, "text1", 0x00000000);
|
||||
assert_background_image (text1, "text1", "st/some-background.png");
|
||||
/* text1 inherits 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", "st/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_pseudo_class (void)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
StTheme *theme;
|
||||
StThemeContext *context;
|
||||
|
||||
clutter_init (&argc, &argv);
|
||||
|
||||
theme = st_theme_new ("st/test-theme.css",
|
||||
NULL, NULL);
|
||||
|
||||
context = st_theme_context_new ();
|
||||
st_theme_context_set_theme (context, theme);
|
||||
st_theme_context_set_resolution (context, 96.);
|
||||
st_theme_context_set_font (context,
|
||||
pango_font_description_from_string ("sans-serif 12"));
|
||||
|
||||
root = st_theme_context_get_root_node (context);
|
||||
group1 = st_theme_node_new (context, root, NULL,
|
||||
CLUTTER_TYPE_GROUP, "group1", NULL, NULL);
|
||||
text1 = st_theme_node_new (context, group1, NULL,
|
||||
CLUTTER_TYPE_TEXT, "text1", "special-text", NULL);
|
||||
text2 = st_theme_node_new (context, group1, NULL,
|
||||
CLUTTER_TYPE_TEXT, "text2", NULL, NULL);
|
||||
group2 = st_theme_node_new (context, root, NULL,
|
||||
CLUTTER_TYPE_GROUP, "group2", NULL, NULL);
|
||||
text3 = st_theme_node_new (context, group2, NULL,
|
||||
CLUTTER_TYPE_TEXT, "text3", NULL, NULL);
|
||||
text4 = st_theme_node_new (context, group2, NULL,
|
||||
CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover");
|
||||
group3 = st_theme_node_new (context, group2, NULL,
|
||||
CLUTTER_TYPE_GROUP, "group3", NULL, "hover");
|
||||
cairo_texture = st_theme_node_new (context, root, NULL,
|
||||
CLUTTER_TYPE_CAIRO_TEXTURE, "cairoTexture", NULL, NULL);
|
||||
|
||||
test_defaults ();
|
||||
test_lengths ();
|
||||
test_classes ();
|
||||
test_type_inheritance ();
|
||||
test_adjacent_selector ();
|
||||
test_background ();
|
||||
test_font ();
|
||||
test_pseudo_class ();
|
||||
|
||||
return fail ? 1 : 0;
|
||||
}
|
68
src/st/test-theme.css
Normal file
68
src/st/test-theme.css
Normal file
@ -0,0 +1,68 @@
|
||||
stage {
|
||||
}
|
||||
|
||||
#group1 {
|
||||
padding: 12pt;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 2em;
|
||||
padding-left: 1in;
|
||||
|
||||
background: #ff0000 url('some-background.png');
|
||||
}
|
||||
|
||||
#text1 {
|
||||
background-image: inherit;
|
||||
}
|
||||
|
||||
.special-text {
|
||||
font-size: 24pt;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ClutterTexture.special-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#text2 {
|
||||
background: inherit;
|
||||
background: none; /* also overrides the color */
|
||||
}
|
||||
|
||||
#group2 {
|
||||
font: italic 12px serif;
|
||||
}
|
||||
|
||||
#text3 {
|
||||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
font-style: oblique;
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
ClutterTexture {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
ClutterCairoTexture {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
#group1 > #text1 {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
stage > #text2 {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
#group2 {
|
||||
background-image: url('other-background.png');
|
||||
}
|
||||
|
||||
ClutterText:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ClutterText:visited {
|
||||
color: #888888;
|
||||
}
|
Loading…
Reference in New Issue
Block a user