diff --git a/AUTHORS b/AUTHORS index c78cefb5..7dd0da98 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,34 @@ -eekboard is written by Daiki Ueno +eekboard is written by Daiki Ueno . The following +files contain code derived from other free software packages: -Cairo keyboard drawing functions are borrowed from the libgnomekbd -library by Sergey V. Udaltsov . See the comments in -eek/eek-drawing.c for detail. +eek/eek-keyboard-drawing.h +eek/eek-keyboard-drawing.c + These files contain code derived from the libgnomekbd library. + Copyright (C) 2006 Sergey V. Udaltsov + +eek/eek-theme-node.h +eek/eek-theme-node.c +eek/eek-theme.h +eek/eek-theme.c + + These files contain code derived from gnome-shell. + + 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 2003-2004 Dodji Seketeli + +data/icons/8x8/Makefile.am +data/icons/16x16/Makefile.am +data/icons/22x22/Makefile.am +data/icons/24x24/Makefile.am +data/icons/32x32/Makefile.am +data/icons/48x48/Makefile.am +data/icons/scalable/Makefile.am + + These files contain code derived from im-chooser. + + Copyright (C) 2006-2008 Red Hat, Inc. All rights reserved. diff --git a/README b/README index 11e8fb97..42f0d2ed 100644 --- a/README +++ b/README @@ -7,7 +7,7 @@ tools to implement desktop virtual keyboards. ** Dependencies -REQUIRED: GLib2, GTK, GConf2, PangoCairo, libxklavier +REQUIRED: GLib2, GTK, GConf2, PangoCairo, libxklavier, libcroco OPTIONAL: fakekey, CSPI, Clutter, Clutter-Gtk, Vala, gobject-introspection ** Build from git repo diff --git a/configure.ac b/configure.ac index 92cf531a..75864118 100644 --- a/configure.ac +++ b/configure.ac @@ -101,6 +101,8 @@ PKG_CHECK_MODULES([XKB], [x11], , [AC_MSG_ERROR([XKB support not found])]) PKG_CHECK_MODULES([LIBXKLAVIER], [libxklavier x11], , [AC_MSG_ERROR([Libxklavier not found])]) +PKG_CHECK_MODULES([LIBCROCO], [libcroco-0.6], , + [AC_MSG_ERROR([libcroco not found])]) dnl use libfakekey to generate key events AC_MSG_CHECKING([whether you enable fakekey]) diff --git a/eek/Makefile.am b/eek/Makefile.am index 7c940e17..800240c9 100644 --- a/eek/Makefile.am +++ b/eek/Makefile.am @@ -39,6 +39,7 @@ libeek_public_headers = \ $(srcdir)/eek-xml.h \ $(srcdir)/eek-xml-layout.h \ $(srcdir)/eek-serializable.h \ + $(srcdir)/eek-theme.h \ $(srcdir)/eek.h libeek_private_headers = \ @@ -46,7 +47,8 @@ libeek_private_headers = \ $(srcdir)/eek-special-keysym-entries.h \ $(srcdir)/eek-unicode-keysym-entries.h \ $(srcdir)/eek-xkeysym-keysym-entries.h \ - $(srcdir)/eek-marshalers.h + $(srcdir)/eek-marshalers.h \ + $(srcdir)/eek-theme-node.h libeek_sources = \ $(srcdir)/eek-layout.c \ @@ -62,7 +64,9 @@ libeek_sources = \ $(srcdir)/eek-xml.c \ $(srcdir)/eek-xml-layout.c \ $(srcdir)/eek-renderer.c \ - $(srcdir)/eek-keyboard-drawing.c + $(srcdir)/eek-keyboard-drawing.c \ + $(srcdir)/eek-theme.c \ + $(srcdir)/eek-theme-node.c libeek_keysym_sources = \ $(srcdir)/eek-special-keysym-entries.h \ @@ -85,8 +89,8 @@ libeek_la_SOURCES = \ $(libeek_sources) \ $(srcdir)/eek-marshalers.c -libeek_la_CFLAGS = $(GIO2_CFLAGS) $(PANGOCAIRO_CFLAGS) -libeek_la_LIBADD = $(GIO2_LIBS) $(PANGOCAIRO_LIBS) -lm +libeek_la_CFLAGS = $(GIO2_CFLAGS) $(PANGOCAIRO_CFLAGS) $(LIBCROCO_CFLAGS) +libeek_la_LIBADD = $(GIO2_LIBS) $(PANGOCAIRO_LIBS) $(LIBCROCO_LIBS) -lm if ENABLE_CLUTTER libeek_clutter_public_headers = \ diff --git a/eek/eek-clutter-keyboard.c b/eek/eek-clutter-keyboard.c index 4fdaa47c..00bc6aba 100644 --- a/eek/eek-clutter-keyboard.c +++ b/eek/eek-clutter-keyboard.c @@ -50,6 +50,7 @@ struct _EekClutterKeyboardPrivate { EekKeyboard *keyboard; EekClutterRenderer *renderer; + EekTheme *theme; }; struct _CreateSectionCallbackData { @@ -78,6 +79,9 @@ eek_clutter_keyboard_real_realize (ClutterActor *self) priv = EEK_CLUTTER_KEYBOARD_GET_PRIVATE(self); + if (priv->theme) + eek_renderer_set_theme (EEK_RENDERER(priv->renderer), priv->theme); + scale = eek_renderer_get_scale (EEK_RENDERER(priv->renderer)); clutter_actor_set_position (CLUTTER_ACTOR(self), bounds.x * scale, @@ -126,7 +130,6 @@ eek_clutter_keyboard_real_allocate (ClutterActor *self, { EekClutterKeyboardPrivate *priv = EEK_CLUTTER_KEYBOARD_GET_PRIVATE(self); - g_assert (priv->renderer); eek_renderer_set_allocation_size (EEK_RENDERER(priv->renderer), box->x2 - box->x1, box->y2 - box->y1); @@ -193,6 +196,11 @@ eek_clutter_keyboard_dispose (GObject *object) priv->keyboard = NULL; } + if (priv->theme) { + g_object_unref (priv->theme); + priv->keyboard = NULL; + } + G_OBJECT_CLASS (eek_clutter_keyboard_parent_class)->dispose (object); } @@ -249,3 +257,16 @@ eek_clutter_keyboard_new (EekKeyboard *keyboard) { return g_object_new (EEK_TYPE_CLUTTER_KEYBOARD, "keyboard", keyboard, NULL); } + +void +eek_clutter_keyboard_set_theme (EekClutterKeyboard *keyboard, + EekTheme *theme) +{ + EekClutterKeyboardPrivate *priv; + + g_return_if_fail (EEK_IS_CLUTTER_KEYBOARD(keyboard)); + g_return_if_fail (EEK_IS_THEME(theme)); + + priv = EEK_CLUTTER_KEYBOARD_GET_PRIVATE(keyboard); + priv->theme = g_object_ref (theme); +} diff --git a/eek/eek-clutter-keyboard.h b/eek/eek-clutter-keyboard.h index 8ad35d57..1d5eae46 100644 --- a/eek/eek-clutter-keyboard.h +++ b/eek/eek-clutter-keyboard.h @@ -54,7 +54,9 @@ struct _EekClutterKeyboardClass }; GType eek_clutter_keyboard_get_type (void) G_GNUC_CONST; -ClutterActor *eek_clutter_keyboard_new (EekKeyboard *keyboard); +ClutterActor *eek_clutter_keyboard_new (EekKeyboard *keyboard); +void eek_clutter_keyboard_set_theme (EekClutterKeyboard *keyboard, + EekTheme *theme); G_END_DECLS #endif /* EEK_CLUTTER_KEYBOARD_H */ diff --git a/eek/eek-clutter-renderer.c b/eek/eek-clutter-renderer.c index 86e05811..f8517b59 100644 --- a/eek/eek-clutter-renderer.c +++ b/eek/eek-clutter-renderer.c @@ -119,7 +119,8 @@ eek_clutter_renderer_render_key (EekClutterRenderer *renderer, CoglHandle *outline_texture; PangoLayout *layout; PangoRectangle extents = { 0, }; - CoglColor color = { 0x00, 0x00, 0x00, 0xFF }; + const EekColor *foreground; + CoglColor color; ClutterGeometry geom; gulong oref; EekKeyboard *keyboard; @@ -200,6 +201,17 @@ eek_clutter_renderer_render_key (EekClutterRenderer *renderer, layout = eek_renderer_create_pango_layout (EEK_RENDERER(renderer)); eek_renderer_render_key_label (EEK_RENDERER(renderer), layout, key); pango_layout_get_extents (layout, NULL, &extents); + + foreground = eek_renderer_get_foreground_color (EEK_RENDERER(renderer), + EEK_ELEMENT(key)); + + cogl_color_set_from_4f (&color, + foreground->red, + foreground->green, + foreground->blue, + foreground->alpha); + eek_color_free (foreground); + cogl_pango_render_layout (layout, (geom.width - extents.width / PANGO_SCALE) / 2, (geom.height - extents.height / PANGO_SCALE) / 2, diff --git a/eek/eek-gtk-keyboard.c b/eek/eek-gtk-keyboard.c index 2fb9b236..3663287a 100644 --- a/eek/eek-gtk-keyboard.c +++ b/eek/eek-gtk-keyboard.c @@ -61,6 +61,7 @@ struct _EekGtkKeyboardPrivate gulong key_pressed_handler; gulong key_released_handler; gulong symbol_index_changed_handler; + EekTheme *theme; }; static EekColor * color_from_gdk_color (GdkColor *gdk_color); @@ -108,6 +109,8 @@ eek_gtk_keyboard_real_draw (GtkWidget *self, pcontext = gtk_widget_get_pango_context (self); priv->renderer = eek_gtk_renderer_new (priv->keyboard, pcontext, self); + if (priv->theme) + eek_renderer_set_theme (priv->renderer, priv->theme); eek_renderer_set_allocation_size (priv->renderer, allocation.width, @@ -117,11 +120,11 @@ eek_gtk_keyboard_real_draw (GtkWidget *self, state = gtk_widget_get_state (self); color = color_from_gdk_color (&style->fg[state]); - eek_renderer_set_foreground (priv->renderer, color); + eek_renderer_set_default_foreground_color (priv->renderer, color); eek_color_free (color); color = color_from_gdk_color (&style->bg[state]); - eek_renderer_set_background (priv->renderer, color); + eek_renderer_set_default_background_color (priv->renderer, color); eek_color_free (color); } @@ -273,7 +276,12 @@ eek_gtk_keyboard_dispose (GObject *object) g_object_unref (priv->keyboard); priv->keyboard = NULL; } - + + if (priv->theme) { + g_object_unref (priv->theme); + priv->theme = NULL; + } + G_OBJECT_CLASS (eek_gtk_keyboard_parent_class)->dispose (object); } @@ -428,3 +436,16 @@ on_symbol_index_changed (EekKeyboard *keyboard, gtk_widget_queue_draw (widget); } + +void +eek_gtk_keyboard_set_theme (EekGtkKeyboard *keyboard, + EekTheme *theme) +{ + EekGtkKeyboardPrivate *priv; + + g_return_if_fail (EEK_IS_GTK_KEYBOARD(keyboard)); + g_return_if_fail (EEK_IS_THEME(theme)); + + priv = EEK_GTK_KEYBOARD_GET_PRIVATE(keyboard); + priv->theme = g_object_ref (theme); +} diff --git a/eek/eek-gtk-keyboard.h b/eek/eek-gtk-keyboard.h index 417c990e..a0473878 100644 --- a/eek/eek-gtk-keyboard.h +++ b/eek/eek-gtk-keyboard.h @@ -55,8 +55,10 @@ struct _EekGtkKeyboardClass gpointer pdummy[24]; }; -GType eek_gtk_keyboard_get_type (void) G_GNUC_CONST; -GtkWidget *eek_gtk_keyboard_new (EekKeyboard *keyboard); +GType eek_gtk_keyboard_get_type (void) G_GNUC_CONST; +GtkWidget *eek_gtk_keyboard_new (EekKeyboard *keyboard); +void eek_gtk_keyboard_set_theme (EekGtkKeyboard *keyboard, + EekTheme *theme); G_END_DECLS #endif /* EEK_GTK_KEYBOARD_H */ diff --git a/eek/eek-renderer.c b/eek/eek-renderer.c index 0414951a..04140a29 100644 --- a/eek/eek-renderer.c +++ b/eek/eek-renderer.c @@ -45,8 +45,8 @@ struct _EekRendererPrivate EekKeyboard *keyboard; PangoContext *pcontext; - EekColor *foreground; - EekColor *background; + EekColor *default_foreground; + EekColor *default_background; gdouble border_width; gdouble allocation_width; @@ -57,6 +57,10 @@ struct _EekRendererPrivate GHashTable *outline_surface_cache; cairo_surface_t *keyboard_surface; gulong symbol_index_changed_handler; + + EekTheme *theme; + /* a mapping from EekElement to EekThemeNode */ + GHashTable *theme_node_hash; }; struct { @@ -147,6 +151,14 @@ create_keyboard_surface (EekRenderer *renderer) EekBounds bounds; cairo_surface_t *keyboard_surface; CreateKeyboardSurfaceCallbackData data; + EekColor *foreground, *background; + + foreground = + eek_renderer_get_foreground_color (renderer, + EEK_ELEMENT(priv->keyboard)); + background = + eek_renderer_get_background_color (renderer, + EEK_ELEMENT(priv->keyboard)); eek_element_get_bounds (EEK_ELEMENT(priv->keyboard), &bounds); keyboard_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, @@ -159,10 +171,10 @@ create_keyboard_surface (EekRenderer *renderer) /* blank background */ cairo_set_source_rgba (data.cr, - priv->background->red, - priv->background->green, - priv->background->blue, - priv->background->alpha); + background->red, + background->green, + background->blue, + background->alpha); cairo_rectangle (data.cr, 0.0, 0.0, @@ -170,11 +182,21 @@ create_keyboard_surface (EekRenderer *renderer) cairo_image_surface_get_height (keyboard_surface)); cairo_fill (data.cr); + cairo_set_source_rgba (data.cr, + foreground->red, + foreground->green, + foreground->blue, + foreground->alpha); + /* draw sections */ eek_container_foreach_child (EEK_CONTAINER(priv->keyboard), create_keyboard_surface_section_callback, &data); cairo_destroy (data.cr); + + eek_color_free (foreground); + eek_color_free (background); + return keyboard_surface; } @@ -190,6 +212,8 @@ render_key_outline (EekRenderer *renderer, gdouble scale; gint i; gulong oref; + EekColor *foreground, *background; + EekGradient *gradient; /* need to rescale so that the border fit inside the clipping region */ @@ -212,25 +236,60 @@ render_key_outline (EekRenderer *renderer, priv->border_width / 2 * priv->scale, priv->border_width / 2 * priv->scale); - /* paint the background with gradient */ - pat = cairo_pattern_create_linear (0.0, - 0.0, - 0.0, - bounds.height * priv->scale * 5.0); - cairo_pattern_add_color_stop_rgba (pat, - 1, - priv->background->red * 0.5, - priv->background->green * 0.5, - priv->background->blue * 0.5, - priv->background->alpha); - cairo_pattern_add_color_stop_rgba (pat, - 0, - priv->background->red, - priv->background->green, - priv->background->blue, - priv->background->alpha); + foreground = eek_renderer_get_foreground_color (renderer, EEK_ELEMENT(key)); + background = eek_renderer_get_background_color (renderer, EEK_ELEMENT(key)); + + gradient = eek_renderer_get_background_gradient (renderer, + EEK_ELEMENT(key)); + + if (gradient) { + switch (gradient->type) { + case EEK_GRADIENT_VERTICAL: + pat = cairo_pattern_create_linear (bounds.width / 2 * priv->scale, + 0.0, + bounds.width / 2 * priv->scale, + bounds.height * priv->scale); + break; + case EEK_GRADIENT_HORIZONTAL: + pat = cairo_pattern_create_linear (0.0, + bounds.height / 2 * priv->scale, + bounds.width * priv->scale, + bounds.height / 2 * priv->scale); + break; + case EEK_GRADIENT_RADIAL: + pat = cairo_pattern_create_radial (0.0, + 0.0, + 0, + 0.0, + 0.0, + MIN(bounds.width, bounds.height) * priv->scale); + break; + default: + g_assert_not_reached (); + break; + } + + cairo_pattern_add_color_stop_rgba (pat, + 1, + gradient->start->red * 0.5, + gradient->start->green * 0.5, + gradient->start->blue * 0.5, + gradient->start->alpha); + cairo_pattern_add_color_stop_rgba (pat, + 0, + gradient->stop->red, + gradient->stop->green, + gradient->stop->blue, + gradient->stop->alpha); + cairo_set_source (cr, pat); + } else { + cairo_set_source_rgba (cr, + background->red, + background->green, + background->blue, + background->alpha); + } - cairo_set_source (cr, pat); _eek_rounded_polygon (cr, outline->corner_radius, outline->points, @@ -239,16 +298,16 @@ render_key_outline (EekRenderer *renderer, cairo_pattern_destroy (pat); - /* paint the border */ + /* paint the border - FIXME: should be configured through theme */ cairo_set_line_width (cr, priv->border_width); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_set_source_rgba (cr, - ABS(priv->background->red - priv->foreground->red) * 0.7, - ABS(priv->background->green - priv->foreground->green) * 0.7, - ABS(priv->background->blue - priv->foreground->blue) * 0.7, - priv->foreground->alpha); + ABS(background->red - foreground->red) * 0.7, + ABS(background->green - foreground->green) * 0.7, + ABS(background->blue - foreground->blue) * 0.7, + foreground->alpha); _eek_rounded_polygon (cr, outline->corner_radius, @@ -257,6 +316,12 @@ render_key_outline (EekRenderer *renderer, cairo_stroke (cr); eek_outline_free (outline); + + eek_color_free (foreground); + eek_color_free (background); + + if (gradient) + eek_gradient_free (gradient); } struct _CalculateFontSizeCallbackData { @@ -400,6 +465,7 @@ render_key (EekRenderer *self, } else { PangoLayout *layout; PangoRectangle extents = { 0, }; + EekColor *foreground; layout = pango_cairo_create_layout (cr); eek_renderer_render_key_label (self, layout, key); @@ -410,11 +476,14 @@ render_key (EekRenderer *self, (cr, (bounds.width * priv->scale - extents.width / PANGO_SCALE) / 2, (bounds.height * priv->scale - extents.height / PANGO_SCALE) / 2); + + foreground = eek_renderer_get_foreground_color (self, EEK_ELEMENT(key)); cairo_set_source_rgba (cr, - priv->foreground->red, - priv->foreground->green, - priv->foreground->blue, - priv->foreground->alpha); + foreground->red, + foreground->green, + foreground->blue, + foreground->alpha); + eek_color_free (foreground); pango_cairo_show_layout (cr, layout); cairo_restore (cr); @@ -535,6 +604,7 @@ eek_renderer_real_render_keyboard (EekRenderer *self, cairo_t *cr) { EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(self); + EekColor *background; g_return_if_fail (priv->keyboard); g_return_if_fail (priv->allocation_width > 0.0); @@ -544,11 +614,15 @@ eek_renderer_real_render_keyboard (EekRenderer *self, priv->keyboard_surface = create_keyboard_surface (self); /* blank background */ + background = eek_renderer_get_background_color (self, + EEK_ELEMENT(priv->keyboard)); cairo_set_source_rgba (cr, - priv->background->red, - priv->background->green, - priv->background->blue, - priv->background->alpha); + background->red, + background->green, + background->blue, + background->alpha); + eek_color_free (background); + cairo_rectangle (cr, 0.0, 0.0, @@ -639,9 +713,10 @@ eek_renderer_finalize (GObject *object) { EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(object); g_hash_table_destroy (priv->outline_surface_cache); - eek_color_free (priv->foreground); - eek_color_free (priv->background); + eek_color_free (priv->default_foreground); + eek_color_free (priv->default_background); pango_font_description_free (priv->font); + g_hash_table_destroy (priv->theme_node_hash); G_OBJECT_CLASS (eek_renderer_parent_class)->finalize (object); } @@ -691,8 +766,8 @@ eek_renderer_init (EekRenderer *self) priv = self->priv = EEK_RENDERER_GET_PRIVATE(self); priv->keyboard = NULL; priv->pcontext = NULL; - priv->foreground = eek_color_new (0.3, 0.3, 0.3, 1.0); - priv->background = eek_color_new (1.0, 1.0, 1.0, 1.0); + priv->default_foreground = eek_color_new (0.3, 0.3, 0.3, 1.0); + priv->default_background = eek_color_new (1.0, 1.0, 1.0, 1.0); priv->border_width = 1.0; priv->allocation_width = 0.0; priv->allocation_height = 0.0; @@ -705,6 +780,11 @@ eek_renderer_init (EekRenderer *self) (GDestroyNotify)cairo_surface_destroy); priv->keyboard_surface = NULL; priv->symbol_index_changed_handler = 0; + priv->theme_node_hash = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)g_object_unref); } static void @@ -956,8 +1036,8 @@ eek_renderer_render_keyboard (EekRenderer *renderer, } void -eek_renderer_set_foreground (EekRenderer *renderer, - EekColor *foreground) +eek_renderer_set_default_foreground_color (EekRenderer *renderer, + EekColor *foreground) { EekRendererPrivate *priv; @@ -965,14 +1045,14 @@ eek_renderer_set_foreground (EekRenderer *renderer, g_return_if_fail (foreground); priv = EEK_RENDERER_GET_PRIVATE(renderer); - if (priv->foreground) - eek_color_free (priv->foreground); - priv->foreground = eek_color_copy (foreground); + if (priv->default_foreground) + eek_color_free (priv->default_foreground); + priv->default_foreground = eek_color_copy (foreground); } void -eek_renderer_set_background (EekRenderer *renderer, - EekColor *background) +eek_renderer_set_default_background_color (EekRenderer *renderer, + EekColor *background) { EekRendererPrivate *priv; @@ -980,35 +1060,66 @@ eek_renderer_set_background (EekRenderer *renderer, g_return_if_fail (background); priv = EEK_RENDERER_GET_PRIVATE(renderer); - if (priv->background) - eek_color_free (priv->background); - priv->background = eek_color_copy (background); + if (priv->default_background) + eek_color_free (priv->default_background); + priv->default_background = eek_color_copy (background); } -void -eek_renderer_get_foreground (EekRenderer *renderer, - EekColor *foreground) +EekColor * +eek_renderer_get_foreground_color (EekRenderer *renderer, EekElement *element) { EekRendererPrivate *priv; + EekThemeNode *theme_node; - g_return_if_fail (EEK_IS_RENDERER(renderer)); - g_return_if_fail (foreground); + g_assert (EEK_IS_RENDERER(renderer)); priv = EEK_RENDERER_GET_PRIVATE(renderer); - *foreground = *priv->foreground; + + theme_node = g_hash_table_lookup (priv->theme_node_hash, element); + if (theme_node) { + EekColor *color = eek_theme_node_get_foreground_color (theme_node); + if (color) + return color; + } + + return eek_color_copy (priv->default_foreground); } -void -eek_renderer_get_background (EekRenderer *renderer, - EekColor *background) +EekColor * +eek_renderer_get_background_color (EekRenderer *renderer, EekElement *element) { EekRendererPrivate *priv; + EekThemeNode *theme_node; - g_return_if_fail (EEK_IS_RENDERER(renderer)); - g_return_if_fail (background); + g_assert (EEK_IS_RENDERER(renderer)); priv = EEK_RENDERER_GET_PRIVATE(renderer); - *background = *priv->background; + + theme_node = g_hash_table_lookup (priv->theme_node_hash, element); + if (theme_node) { + EekColor *color = eek_theme_node_get_background_color (theme_node); + if (color) + return color; + } + + return eek_color_copy (priv->default_background); +} + +EekGradient * +eek_renderer_get_background_gradient (EekRenderer *renderer, EekElement *element) +{ + EekRendererPrivate *priv; + EekThemeNode *theme_node; + + g_assert (EEK_IS_RENDERER(renderer)); + + priv = EEK_RENDERER_GET_PRIVATE(renderer); + + theme_node = g_hash_table_lookup (priv->theme_node_hash, element); + if (theme_node) + return eek_theme_node_get_background_gradient (theme_node); + + return NULL; } struct _FindKeyByPositionCallbackData { @@ -1136,3 +1247,91 @@ eek_renderer_find_key_by_position (EekRenderer *renderer, &data); return data.key; } + +struct _CreateThemeNodeData { + EekThemeNode *parent; + EekRenderer *renderer; +}; +typedef struct _CreateThemeNodeData CreateThemeNodeData; + +void +create_theme_node_key_callback (EekElement *element, + gpointer user_data) +{ + CreateThemeNodeData *data = user_data; + EekRendererPrivate *priv; + EekThemeNode *node; + + priv = EEK_RENDERER_GET_PRIVATE(data->renderer); + + node = eek_theme_node_new (data->parent, + priv->theme, + EEK_TYPE_KEY, + eek_element_get_name (element), + "key", + NULL, + NULL); + g_hash_table_insert (priv->theme_node_hash, element, node); +} + +void +create_theme_node_section_callback (EekElement *element, + gpointer user_data) +{ + CreateThemeNodeData *data = user_data; + EekRendererPrivate *priv; + EekThemeNode *node, *parent; + + priv = EEK_RENDERER_GET_PRIVATE(data->renderer); + + node = eek_theme_node_new (data->parent, + priv->theme, + EEK_TYPE_SECTION, + eek_element_get_name (element), + "section", + NULL, + NULL); + g_hash_table_insert (priv->theme_node_hash, element, node); + + parent = data->parent; + data->parent = node; + eek_container_foreach_child (EEK_CONTAINER(element), + create_theme_node_key_callback, + data); + data->parent = parent; +} + +void +eek_renderer_set_theme (EekRenderer *renderer, + EekTheme *theme) +{ + EekRendererPrivate *priv; + EekThemeNode *node; + CreateThemeNodeData data; + + g_assert (EEK_IS_RENDERER(renderer)); + g_assert (EEK_IS_THEME(theme)); + + priv = EEK_RENDERER_GET_PRIVATE(renderer); + + if (priv->theme) + g_object_unref (priv->theme); + priv->theme = g_object_ref (theme); + + g_hash_table_remove_all (priv->theme_node_hash); + + node = eek_theme_node_new (NULL, + priv->theme, + EEK_TYPE_KEYBOARD, + "keyboard", + "keyboard", + NULL, + NULL); + g_hash_table_insert (priv->theme_node_hash, priv->keyboard, node); + + data.parent = node; + data.renderer = renderer; + eek_container_foreach_child (EEK_CONTAINER(priv->keyboard), + create_theme_node_section_callback, + &data); +} diff --git a/eek/eek-renderer.h b/eek/eek-renderer.h index 7969c577..d288c7ea 100644 --- a/eek/eek-renderer.h +++ b/eek/eek-renderer.h @@ -25,6 +25,7 @@ #include "eek-keyboard.h" #include "eek-keysym.h" #include "eek-types.h" +#include "eek-theme.h" G_BEGIN_DECLS @@ -79,67 +80,74 @@ struct _EekRendererClass gpointer pdummy[23]; }; -GType eek_renderer_get_type (void) G_GNUC_CONST; -EekRenderer *eek_renderer_new (EekKeyboard *keyboard, - PangoContext *pcontext); -void eek_renderer_set_allocation_size (EekRenderer *renderer, - gdouble width, - gdouble height); -void eek_renderer_get_size (EekRenderer *renderer, - gdouble *width, - gdouble *height); -void eek_renderer_get_key_bounds (EekRenderer *renderer, - EekKey *key, - EekBounds *bounds, - gboolean rotate); +GType eek_renderer_get_type (void) G_GNUC_CONST; +EekRenderer *eek_renderer_new (EekKeyboard *keyboard, + PangoContext *pcontext); +void eek_renderer_set_allocation_size (EekRenderer *renderer, + gdouble width, + gdouble height); +void eek_renderer_get_size (EekRenderer *renderer, + gdouble *width, + gdouble *height); +void eek_renderer_get_key_bounds (EekRenderer *renderer, + EekKey *key, + EekBounds *bounds, + gboolean rotate); -gdouble eek_renderer_get_scale (EekRenderer *renderer); +gdouble eek_renderer_get_scale (EekRenderer *renderer); -PangoLayout *eek_renderer_create_pango_layout (EekRenderer *renderer); -void eek_renderer_render_key_label (EekRenderer *renderer, - PangoLayout *layout, - EekKey *key); +PangoLayout *eek_renderer_create_pango_layout (EekRenderer *renderer); +void eek_renderer_render_key_label (EekRenderer *renderer, + PangoLayout *layout, + EekKey *key); -void eek_renderer_render_key_outline (EekRenderer *renderer, - cairo_t *cr, - EekKey *key, - gdouble scale, - gboolean rotate); +void eek_renderer_render_key_outline (EekRenderer *renderer, + cairo_t *cr, + EekKey *key, + gdouble scale, + gboolean rotate); -void eek_renderer_render_key (EekRenderer *renderer, - cairo_t *cr, - EekKey *key, - gdouble scale, - gboolean rotate); +void eek_renderer_render_key (EekRenderer *renderer, + cairo_t *cr, + EekKey *key, + gdouble scale, + gboolean rotate); -void eek_renderer_render_key_icon (EekRenderer *renderer, - cairo_t *cr, - EekKey *key, - gdouble scale, - gboolean rotate); +void eek_renderer_render_key_icon (EekRenderer *renderer, + cairo_t *cr, + EekKey *key, + gdouble scale, + gboolean rotate); -void eek_renderer_render_keyboard (EekRenderer *renderer, - cairo_t *cr); +void eek_renderer_render_keyboard (EekRenderer *renderer, + cairo_t *cr); -void eek_renderer_set_foreground (EekRenderer *renderer, - EekColor *foreground); -void eek_renderer_set_background (EekRenderer *renderer, - EekColor *background); -void eek_renderer_get_foreground (EekRenderer *renderer, - EekColor *foreground); -void eek_renderer_get_background (EekRenderer *renderer, - EekColor *background); -void eek_renderer_set_border_width (EekRenderer *renderer, - gdouble border_width); -EekKey *eek_renderer_find_key_by_position (EekRenderer *renderer, - gdouble x, - gdouble y); +void eek_renderer_set_default_foreground_color + (EekRenderer *renderer, + EekColor *foreground); +void eek_renderer_set_default_background_color + (EekRenderer *renderer, + EekColor *background); +EekColor *eek_renderer_get_foreground_color (EekRenderer *renderer, + EekElement *element); +EekColor *eek_renderer_get_background_color (EekRenderer *renderer, + EekElement *element); +EekGradient *eek_renderer_get_background_gradient (EekRenderer *renderer, + EekElement *element); +void eek_renderer_set_border_width (EekRenderer *renderer, + gdouble border_width); +EekKey *eek_renderer_find_key_by_position (EekRenderer *renderer, + gdouble x, + gdouble y); void eek_renderer_apply_transformation_for_key - (EekRenderer *renderer, - cairo_t *cr, - EekKey *key, - gdouble scale, - gboolean rotate); + (EekRenderer *renderer, + cairo_t *cr, + EekKey *key, + gdouble scale, + gboolean rotate); + +void eek_renderer_set_theme (EekRenderer *renderer, + EekTheme *theme); G_END_DECLS #endif /* EEK_RENDERER_H */ diff --git a/eek/eek-theme-node.c b/eek/eek-theme-node.c new file mode 100644 index 00000000..953b0d6e --- /dev/null +++ b/eek/eek-theme-node.c @@ -0,0 +1,745 @@ +/* + * 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 + * + * 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 . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include + +#include "eek-theme-node.h" +#include "eek-theme-private.h" + +G_DEFINE_TYPE (EekThemeNode, eek_theme_node, G_TYPE_OBJECT); + +#define EEK_THEME_NODE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEK_TYPE_THEME_NODE, EekThemeNodePrivate)) + +struct _EekThemeNodePrivate +{ + EekThemeNode *parent_node; + EekTheme *theme; + + EekColor *transparent_color; + EekColor *black_color; + + EekGradientType background_gradient_type; + + EekColor *background_color; + EekColor *background_gradient_end; + EekColor *foreground_color; + + GType element_type; + char *element_id; + char *element_class; + char *pseudo_class; + char *inline_style; + + CRDeclaration **properties; + int n_properties; + + /* We hold onto these separately so we can destroy them on finalize */ + CRDeclaration *inline_properties; + + guint properties_computed : 1; + guint background_computed : 1; + guint foreground_computed : 1; +}; + +static void +eek_theme_node_dispose (GObject *gobject) +{ + EekThemeNodePrivate *priv = EEK_THEME_NODE_GET_PRIVATE (gobject); + + if (priv->theme) { + g_object_unref (priv->theme); + priv->theme = NULL; + } + + if (priv->parent_node) { + g_object_unref (priv->parent_node); + priv->parent_node = NULL; + } + + G_OBJECT_CLASS (eek_theme_node_parent_class)->dispose (gobject); +} + +static void +eek_theme_node_finalize (GObject *object) +{ + EekThemeNodePrivate *priv = EEK_THEME_NODE_GET_PRIVATE (object); + + eek_color_free (priv->transparent_color); + eek_color_free (priv->black_color); + eek_color_free (priv->background_color); + eek_color_free (priv->background_gradient_end); + eek_color_free (priv->foreground_color); + + g_free (priv->element_id); + g_free (priv->element_class); + g_free (priv->pseudo_class); + g_free (priv->inline_style); + + if (priv->properties) { + g_free (priv->properties); + priv->properties = NULL; + priv->n_properties = 0; + } + + if (priv->inline_properties) { + /* This destroys the list, not just the head of the list */ + cr_declaration_destroy (priv->inline_properties); + } + + G_OBJECT_CLASS (eek_theme_node_parent_class)->finalize (object); +} + +static void +eek_theme_node_class_init (EekThemeNodeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (gobject_class, + sizeof (EekThemeNodePrivate)); + + gobject_class->dispose = eek_theme_node_dispose; + gobject_class->finalize = eek_theme_node_finalize; +} + +static void +eek_theme_node_init (EekThemeNode *self) +{ + EekThemeNodePrivate *priv; + + priv = self->priv = EEK_THEME_NODE_GET_PRIVATE(self); + + priv->transparent_color = eek_color_new (0.0, 0.0, 0.0, 0.0); + priv->black_color = eek_color_new (0.0, 0.0, 0.0, 1.0); + priv->background_color = eek_color_copy (priv->transparent_color); + priv->background_gradient_end = eek_color_copy (priv->transparent_color); + priv->foreground_color = eek_color_copy (priv->black_color); + priv->background_gradient_type = EEK_GRADIENT_NONE; +} + +/** + * eek_theme_node_new: + * @parent_node: (allow-none): the parent node of this node + * @theme: (allow-none): 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: (allow-none): the ID to match CSS rules against + * @element_class: (allow-none): a whitespace-separated list of classes + * to match CSS rules against + * @pseudo_class: (allow-none): a whitespace-separated list of pseudo-classes + * (like 'hover' or 'visited') to match CSS rules against + * + * Creates a new #EekThemeNode. Once created, a node is immutable. Of any + * of the attributes of the node (like the @element_class) change the node + * and its child nodes must be destroyed and recreated. + * + * Return value: (transfer full): the theme node + */ +EekThemeNode * +eek_theme_node_new (EekThemeNode *parent_node, + EekTheme *theme, + GType element_type, + const char *element_id, + const char *element_class, + const char *pseudo_class, + const char *inline_style) +{ + EekThemeNode *node; + EekThemeNodePrivate *priv; + + g_return_val_if_fail (parent_node == NULL || EEK_IS_THEME_NODE (parent_node), NULL); + + node = g_object_new (EEK_TYPE_THEME_NODE, NULL); + priv = EEK_THEME_NODE_GET_PRIVATE(node); + + if (parent_node != NULL) + priv->parent_node = g_object_ref (parent_node); + else + priv->parent_node = NULL; + + if (theme == NULL && parent_node != NULL) + theme = eek_theme_node_get_theme (parent_node); + + if (theme != NULL) + priv->theme = g_object_ref (theme); + + priv->element_type = element_type; + priv->element_id = g_strdup (element_id); + priv->element_class = g_strdup (element_class); + priv->pseudo_class = g_strdup (pseudo_class); + priv->inline_style = g_strdup (inline_style); + + return node; +} + +/** + * eek_theme_node_get_parent: + * @node: a #EekThemeNode + * + * Gets the parent themed element node. + * + * Return value: (transfer none): the parent #EekThemeNode, or %NULL if this + * is the root node of the tree of theme elements. + */ +EekThemeNode * +eek_theme_node_get_parent (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_return_val_if_fail (EEK_IS_THEME_NODE (node), NULL); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + return priv->parent_node; +} + +/** + * eek_theme_node_get_theme: + * @node: a #EekThemeNode + * + * Gets the theme stylesheet set that styles this node + * + * Return value: (transfer none): the theme stylesheet set + */ +EekTheme * +eek_theme_node_get_theme (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_return_val_if_fail (EEK_IS_THEME_NODE (node), NULL); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + return priv->theme; +} + +GType +eek_theme_node_get_element_type (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_return_val_if_fail (EEK_IS_THEME_NODE (node), G_TYPE_NONE); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + return priv->element_type; +} + +const char * +eek_theme_node_get_element_id (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_return_val_if_fail (EEK_IS_THEME_NODE (node), NULL); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + return priv->element_id; +} + +const char * +eek_theme_node_get_element_class (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_return_val_if_fail (EEK_IS_THEME_NODE (node), NULL); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + return priv->element_class; +} + +const char * +eek_theme_node_get_pseudo_class (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_return_val_if_fail (EEK_IS_THEME_NODE (node), NULL); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + return priv->pseudo_class; +} + +static void +ensure_properties (EekThemeNode *node) +{ + EekThemeNodePrivate *priv = EEK_THEME_NODE_GET_PRIVATE(node); + + if (!priv->properties_computed) { + GPtrArray *properties = NULL; + + priv->properties_computed = TRUE; + + if (priv->theme) + properties = _eek_theme_get_matched_properties (priv->theme, node); + + if (priv->inline_style) { + CRDeclaration *cur_decl; + + if (!properties) + properties = g_ptr_array_new (); + + priv->inline_properties = + _eek_theme_parse_declaration_list (priv->inline_style); + for (cur_decl = priv->inline_properties; + cur_decl; + cur_decl = cur_decl->next) + g_ptr_array_add (properties, cur_decl); + } + + if (properties) { + priv->n_properties = properties->len; + priv->properties = (CRDeclaration **)g_ptr_array_free (properties, + FALSE); + } + } +} + +typedef enum { + VALUE_FOUND, + VALUE_NOT_FOUND, + VALUE_INHERIT +} GetFromTermResult; + +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 GetFromTermResult +get_color_from_rgba_term (CRTerm *term, + EekColor **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; + } + + arg = arg->next; + } + + *color = eek_color_new (CLAMP(r, 0.0, 1.0), + CLAMP(g, 0.0, 1.0), + CLAMP(b, 0.0, 1.0), + CLAMP(a, 0.0, 1.0)); + + return VALUE_FOUND; +} + +static GetFromTermResult +get_color_from_term (EekThemeNode *node, + CRTerm *term, + EekColor **color) +{ + EekThemeNodePrivate *priv = EEK_THEME_NODE_GET_PRIVATE(node); + CRRgb rgb; + enum CRStatus status; + + /* Since libcroco doesn't know about rgba colors, it can't handle + * the transparent keyword + */ + if (term_is_transparent (term)) { + *color = eek_color_copy (priv->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. + * + * libcroco < 0.6.2 has a bug where functions starting with 'r' are + * misparsed. We workaround this by pre-converting 'rgba' to 'RGBA' + * before parsing the stylesheet. Since libcroco isn't + * case-insensitive (a bug), it's fine with functions starting with + * 'R'. (In theory, we should be doing a case-insensitive compare + * everywhere, not just here, but that doesn't make much sense when + * the built-in parsing of libcroco is case-sensitive and things + * like 10PX don't work.) + */ + else if (term->type == TERM_FUNCTION && + term->content.str && + term->content.str->stryng && + term->content.str->stryng->str && + g_ascii_strcasecmp (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.inherit) + return VALUE_INHERIT; + + if (rgb.is_percentage) + cr_rgb_compute_from_percentage (&rgb); + + *color = eek_color_new (rgb.red / (gdouble)0xff, + rgb.green / (gdouble)0xff, + rgb.blue / (gdouble)0xff, + 1.0); + + return VALUE_FOUND; +} + +/** + * eek_theme_node_get_color: + * @node: a #EekThemeNode + * @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: 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 eek_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. + * + * Return value: %TRUE if the property was found in the properties for this + * theme node (or in the properties of parent nodes when inheriting.) + */ +gboolean +eek_theme_node_get_color (EekThemeNode *node, + const char *property_name, + gboolean inherit, + EekColor **color) +{ + EekThemeNodePrivate *priv; + int i; + + g_return_val_if_fail (EEK_IS_THEME_NODE(node), FALSE); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + + ensure_properties (node); + + for (i = priv->n_properties - 1; i >= 0; i--) { + CRDeclaration *decl = priv->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 (priv->parent_node) + return eek_theme_node_get_color (priv->parent_node, property_name, inherit, color); + else + break; + } + } + } + + return FALSE; +} + +static GetFromTermResult +get_background_color_from_term (EekThemeNode *node, + CRTerm *term, + EekColor **color) +{ + EekThemeNodePrivate *priv = EEK_THEME_NODE_GET_PRIVATE(node); + GetFromTermResult result = get_color_from_term (node, term, color); + if (result == VALUE_NOT_FOUND) { + if (term_is_transparent (term)) { + *color = eek_color_copy (priv->transparent_color); + return VALUE_FOUND; + } + } + + return result; +} + +void +_eek_theme_node_ensure_background (EekThemeNode *node) +{ + EekThemeNodePrivate *priv = EEK_THEME_NODE_GET_PRIVATE(node); + int i; + + if (priv->background_computed) + return; + + priv->background_computed = TRUE; + + eek_color_free (priv->background_color); + priv->background_color = eek_color_copy (priv->transparent_color); + eek_color_free (priv->background_gradient_end); + priv->background_gradient_end = eek_color_copy (priv->background_color); + priv->background_gradient_type = EEK_GRADIENT_NONE; + + ensure_properties (node); + + for (i = 0; i < priv->n_properties; i++) { + CRDeclaration *decl = priv->properties[i]; + const char *property_name = decl->property->stryng->str; + GetFromTermResult result; + EekColor *color; + + 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 */ + eek_color_free (priv->background_color); + priv->background_color = eek_color_copy (priv->transparent_color); + + for (term = decl->value; term; term = term->next) { + result = get_background_color_from_term (node, term, &color); + if (result == VALUE_FOUND) { + eek_color_free (priv->background_color); + priv->background_color = color; + } else if (result == VALUE_INHERIT) { + if (priv->parent_node) { + color = eek_theme_node_get_background_color (priv->parent_node); + if (color) { + eek_color_free (priv->background_color); + priv->background_color = color; + } + } + } else if (term_is_none (term)) { + /* leave priv->background_color as transparent */ + } + } + } else if (strcmp (property_name, "-color") == 0) { + if (decl->value == NULL || decl->value->next != NULL) + continue; + + result = get_background_color_from_term (node, decl->value, &color); + if (result == VALUE_FOUND) { + eek_color_free (priv->background_color); + priv->background_color = color; + } else if (result == VALUE_INHERIT) { + if (priv->parent_node) { + color = + eek_theme_node_get_background_color (priv->parent_node); + if (color) { + eek_color_free (priv->background_color); + priv->background_color = color; + } + } + } + } else if (strcmp (property_name, "-gradient-direction") == 0) { + CRTerm *term = decl->value; + if (strcmp (term->content.str->stryng->str, "vertical") == 0) { + priv->background_gradient_type = EEK_GRADIENT_VERTICAL; + } else if (strcmp (term->content.str->stryng->str, "horizontal") == 0) { + priv->background_gradient_type = EEK_GRADIENT_HORIZONTAL; + } else if (strcmp (term->content.str->stryng->str, "radial") == 0) { + priv->background_gradient_type = EEK_GRADIENT_RADIAL; + } else if (strcmp (term->content.str->stryng->str, "none") == 0) { + priv->background_gradient_type = EEK_GRADIENT_NONE; + } else { + g_warning ("Unrecognized background-gradient-direction \"%s\"", + term->content.str->stryng->str); + } + } else if (strcmp (property_name, "-gradient-start") == 0) { + result = get_color_from_term (node, decl->value, &color); + if (result == VALUE_FOUND) { + eek_color_free (priv->background_color); + priv->background_color = color; + } + } else if (strcmp (property_name, "-gradient-end") == 0) { + result = get_color_from_term (node, decl->value, &color); + if (result == VALUE_FOUND) { + eek_color_free (priv->background_gradient_end); + priv->background_gradient_end = color; + } + } + } +} + +EekColor * +eek_theme_node_get_background_color (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_assert (EEK_IS_THEME_NODE (node)); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + + _eek_theme_node_ensure_background (node); + + if (priv->background_gradient_type == EEK_GRADIENT_NONE) + return eek_color_copy (priv->background_color); + return NULL; +} + +EekColor * +eek_theme_node_get_foreground_color (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_assert (EEK_IS_THEME_NODE (node)); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + + if (!priv->foreground_computed) { + int i; + EekColor *color; + + priv->foreground_computed = TRUE; + + ensure_properties (node); + + for (i = priv->n_properties - 1; i >= 0; i--) { + CRDeclaration *decl = priv->properties[i]; + + if (strcmp (decl->property->stryng->str, "color") == 0) { + GetFromTermResult result = + get_color_from_term (node, decl->value, &color); + if (result == VALUE_FOUND) { + eek_color_free (priv->foreground_color); + priv->foreground_color = color; + goto out; + } + if (result == VALUE_INHERIT) + break; + } + } + + if (priv->parent_node) { + color = eek_theme_node_get_foreground_color (priv->parent_node); + if (color) { + eek_color_free (priv->foreground_color); + priv->foreground_color = color; + } + } else { + /* default to black */ + eek_color_free (priv->foreground_color); + priv->foreground_color = eek_color_copy (priv->black_color); + } + } + + out: + return eek_color_copy (priv->foreground_color); +} + +/** + * eek_theme_node_get_background_gradient: + * @node: A #EekThemeNode + * + * Get the background gradient of @node. If the node does not have + * gradient property, returns %NULL. + * + * Returns: an #EekGradient, which should be freed with + * eek_gradient_free() or %NULL. + */ +EekGradient * +eek_theme_node_get_background_gradient (EekThemeNode *node) +{ + EekThemeNodePrivate *priv; + + g_assert (EEK_IS_THEME_NODE (node)); + + priv = EEK_THEME_NODE_GET_PRIVATE(node); + + _eek_theme_node_ensure_background (node); + + if (priv->background_gradient_type == EEK_GRADIENT_NONE) + return NULL; + + return eek_gradient_new (priv->background_gradient_type, + priv->background_color, + priv->background_gradient_end); +} diff --git a/eek/eek-theme-node.h b/eek/eek-theme-node.h new file mode 100644 index 00000000..970c32a9 --- /dev/null +++ b/eek/eek-theme-node.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * Copyright (C) 2010-2011 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef __EEK_THEME_NODE_H__ +#define __EEK_THEME_NODE_H__ + +#include "eek-types.h" + +G_BEGIN_DECLS + +/** + * SECTION:EekThemeNode + * @short_description: style information for one node in a tree of + * themed objects + * + * A #EekThemeNode 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 + * #EekElement. A #EekThemeNode 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. #EekThemeNode 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. #EekThemeNode also has + * convenience functions to help in implementing a #EekElement with + * borders and padding. + */ + +#define EEK_TYPE_THEME_NODE (eek_theme_node_get_type()) +#define EEK_THEME_NODE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEK_TYPE_THEME_NODE, EekThemeNode)) +#define EEK_THEME_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEK_TYPE_THEME_NODE, EekThemeNodeClass)) +#define EEK_IS_THEME_NODE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEK_TYPE_THEME_NODE)) +#define EEK_IS_THEME_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEK_TYPE_THEME_NODE)) +#define EEK_THEME_NODE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEK_TYPE_THEME_NODE, EekThemeNodeClass)) + +typedef struct _EekThemeNodeClass EekThemeNodeClass; +typedef struct _EekThemeNodePrivate EekThemeNodePrivate; + +struct _EekThemeNode { + GObject parent; + + EekThemeNodePrivate *priv; +}; + +struct _EekThemeNodeClass { + GObjectClass parent_class; +}; + +GType eek_theme_node_get_type + (void) G_GNUC_CONST; + +EekThemeNode *eek_theme_node_new (EekThemeNode *parent_node, + /* can be null */ EekTheme *theme, + /* can be null */ GType element_type, + const char *element_id, + const char *element_class, + const char *pseudo_class, + const char *inline_style); + +EekThemeNode *eek_theme_node_get_parent + (EekThemeNode *node); + +EekTheme *eek_theme_node_get_theme + (EekThemeNode *node); + +GType eek_theme_node_get_element_type + (EekThemeNode *node); +const char *eek_theme_node_get_element_id + (EekThemeNode *node); +const char *eek_theme_node_get_element_class + (EekThemeNode *node); +const char *eek_theme_node_get_pseudo_class + (EekThemeNode *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 eek_theme_node_get_color + (EekThemeNode *node, + const char *property_name, + gboolean inherit, + EekColor **color); + +/* Specific getters for particular properties: cached + */ +EekColor *eek_theme_node_get_background_color + (EekThemeNode *node); +EekColor *eek_theme_node_get_foreground_color + (EekThemeNode *node); +EekGradient *eek_theme_node_get_background_gradient + (EekThemeNode *node); + +G_END_DECLS + +#endif /* __EEK_THEME_NODE_H__ */ diff --git a/eek/eek-theme.c b/eek/eek-theme.c new file mode 100644 index 00000000..f41c9827 --- /dev/null +++ b/eek/eek-theme.c @@ -0,0 +1,1130 @@ +/* -*- 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 + * + * In moving it to libeek: + * - Renamed again to EekTheme + */ + +/* + * 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 +#include + +#include + +#include "eek-theme.h" +#include "eek-theme-node.h" +#include "eek-theme-private.h" + +static GObject *eek_theme_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); + +static void eek_theme_finalize (GObject *object); +static void eek_theme_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void eek_theme_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +struct _EekTheme +{ + GObject parent; + + char *application_stylesheet; + char *default_stylesheet; + char *theme_stylesheet; + GSList *custom_stylesheets; + + GHashTable *stylesheets_by_filename; + GHashTable *filenames_by_stylesheet; + + CRCascade *cascade; +}; + +struct _EekThemeClass +{ + GObjectClass parent_class; +}; + +enum +{ + PROP_0, + PROP_APPLICATION_STYLESHEET, + PROP_THEME_STYLESHEET, + PROP_DEFAULT_STYLESHEET +}; + +G_DEFINE_TYPE (EekTheme, eek_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 +eek_theme_init (EekTheme *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 +eek_theme_class_init (EekThemeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = eek_theme_constructor; + object_class->finalize = eek_theme_finalize; + object_class->set_property = eek_theme_set_property; + object_class->get_property = eek_theme_get_property; + + /** + * EekTheme: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)); + + /** + * EekTheme: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)); + + /** + * EekTheme: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)); + +} + +/* This is a workaround for a bug in libcroco < 0.6.2 where + * function starting with 'r' (and 'u') are misparsed. We work + * around this by exploiting the fact that libcroco is incomformant + * with the CSS-spec and case sensitive and pre-convert all + * occurrences of rgba to RGBA. Then we make our own parsing + * code check for RGBA as well. + */ +#if LIBCROCO_VERSION_NUMBER < 602 +static gboolean +is_identifier_character (char c) +{ + /* Actual CSS rules allow for unicode > 0x00a1 and escaped + * characters, but we'll assume we won't do that in our stylesheets + * or at least not next to the string 'rgba'. + */ + return g_ascii_isalnum(c) || c == '-' || c == '_'; +} + +static void +convert_rgba_RGBA (char *buf) +{ + char *p; + + p = strstr (buf, "rgba"); + while (p) + { + /* Check if this looks like a complete token; this is to + * avoiding mangling, say, a selector '.rgba-entry' */ + if (!((p > buf && is_identifier_character (*(p - 1))) || + (is_identifier_character (*(p + 4))))) + memcpy(p, "RGBA", 4); + p += 4; + p = strstr (p, "rgba"); + } +} + +static CRStyleSheet * +parse_stylesheet (const char *filename, + GError **error) +{ + enum CRStatus status; + char *contents; + gsize length; + CRStyleSheet *stylesheet = NULL; + + if (filename == NULL) + return NULL; + + if (!g_file_get_contents (filename, &contents, &length, error)) + return NULL; + + convert_rgba_RGBA (contents); + + status = cr_om_parser_simply_parse_buf ((const guchar *) contents, + length, + CR_UTF_8, + &stylesheet); + g_free (contents); + + if (status != CR_OK) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Error parsing stylesheet '%s'; errcode:%d", filename, status); + return NULL; + } + + return stylesheet; +} + +CRDeclaration * +_eek_theme_parse_declaration_list (const char *str) +{ + char *copy = g_strdup (str); + CRDeclaration *result; + + convert_rgba_RGBA (copy); + + result = cr_declaration_parse_list_from_buf ((const guchar *)copy, + CR_UTF_8); + g_free (copy); + + return result; +} +#else /* LIBCROCO_VERSION_NUMBER >= 602 */ +static CRStyleSheet * +parse_stylesheet (const char *filename, + GError **error) +{ + 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_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Error parsing stylesheet '%s'; errcode:%d", filename, status); + return NULL; + } + + return stylesheet; +} + +CRDeclaration * +_eek_theme_parse_declaration_list (const char *str) +{ + return cr_declaration_parse_list_from_buf ((const guchar *)str, + CR_UTF_8); +} +#endif /* LIBCROCO_VERSION_NUMBER < 602 */ + +/* Just g_warning for now until we have something nicer to do */ +static CRStyleSheet * +parse_stylesheet_nofail (const char *filename) +{ + GError *error = NULL; + CRStyleSheet *result; + + result = parse_stylesheet (filename, &error); + if (error) + { + g_warning ("%s", error->message); + g_clear_error (&error); + } + return result; +} + +static void +insert_stylesheet (EekTheme *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); +} + +gboolean +eek_theme_load_stylesheet (EekTheme *theme, + const char *path, + GError **error) +{ + CRStyleSheet *stylesheet; + + stylesheet = parse_stylesheet (path, error); + if (!stylesheet) + return FALSE; + + insert_stylesheet (theme, path, stylesheet); + theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet); + + return TRUE; +} + +void +eek_theme_unload_stylesheet (EekTheme *theme, + const char *path) +{ + CRStyleSheet *stylesheet; + + stylesheet = g_hash_table_lookup (theme->stylesheets_by_filename, path); + if (!stylesheet) + return; + + if (!g_slist_find (theme->custom_stylesheets, stylesheet)) + return; + + theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet); + g_hash_table_remove (theme->stylesheets_by_filename, path); + g_hash_table_remove (theme->filenames_by_stylesheet, stylesheet); + cr_stylesheet_unref (stylesheet); +} + +static GObject * +eek_theme_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *object; + EekTheme *theme; + CRStyleSheet *application_stylesheet; + CRStyleSheet *theme_stylesheet; + CRStyleSheet *default_stylesheet; + + object = (*G_OBJECT_CLASS (eek_theme_parent_class)->constructor) (type, + n_construct_properties, + construct_properties); + theme = EEK_THEME (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); + + return object; +} + +static void +eek_theme_finalize (GObject * object) +{ + EekTheme *theme = EEK_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_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 (eek_theme_parent_class)->finalize (object); +} + +static void +eek_theme_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EekTheme *theme = EEK_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 +eek_theme_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EekTheme *theme = EEK_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; + } +} + +/** + * eek_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 + **/ +EekTheme * +eek_theme_new (const char *application_stylesheet, + const char *theme_stylesheet, + const char *default_stylesheet) +{ + EekTheme *theme = g_object_new (EEK_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 (EekTheme *a_this, + CRAdditionalSel *a_add_sel, + EekThemeNode *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 = eek_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, + EekThemeNode *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 = eek_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, + EekThemeNode *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 = eek_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 (EekTheme *a_this, + CRAdditionalSel *a_add_sel, + EekThemeNode *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 (EekTheme *a_this, + CRSimpleSel *a_sel, + EekThemeNode *a_node, + gboolean *a_result, + gboolean a_eval_sel_list_from_end, + gboolean a_recurse) +{ + CRSimpleSel *cur_sel = NULL; + EekThemeNode *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 = eek_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 */ + { + EekThemeNode *n = NULL; + + /* + *walk the element tree upward looking for a parent + *style that matches the preceding selector. + */ + for (n = eek_theme_node_get_parent (a_node); n; n = eek_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 = eek_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 = eek_theme_node_get_parent (cur_node); + if (!cur_node) + goto done; + cur_type = eek_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 (EekTheme *a_this, + CRStyleSheet *a_nodesheet, + EekThemeNode *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 = _eek_theme_resolve_url (a_this, + a_nodesheet, + import_rule->url->stryng->str); + + if (filename) + import_rule->sheet = parse_stylesheet (filename, NULL); + + 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; +} + +GPtrArray * +_eek_theme_get_matched_properties (EekTheme *theme, + EekThemeNode *node) +{ + enum CRStyleOrigin origin = 0; + CRStyleSheet *sheet = NULL; + GPtrArray *props = g_ptr_array_new (); + GSList *iter; + + g_return_val_if_fail (EEK_IS_THEME (theme), NULL); + g_return_val_if_fail (EEK_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 an absolute + * local filename, if possible. The resolution here is distinctly lame and + * will fail on many examples. + */ +char * +_eek_theme_resolve_url (EekTheme *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; +} diff --git a/eek/eek-theme.h b/eek/eek-theme.h new file mode 100644 index 00000000..4065641c --- /dev/null +++ b/eek/eek-theme.h @@ -0,0 +1,44 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __EEK_THEME_H__ +#define __EEK_THEME_H__ + +#include + +#include "eek-theme-node.h" + +G_BEGIN_DECLS + +/** + * SECTION:EekTheme + * @short_description: a set of stylesheets + * + * #EekTheme holds a set of stylesheets. (The "cascade" of the name + * Cascading Stylesheets.) An #EekTheme can be set to apply to all the + * keyboard elements. + */ + +typedef struct _EekThemeClass EekThemeClass; + +#define EEK_TYPE_THEME (eek_theme_get_type()) +#define EEK_THEME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEK_TYPE_THEME, EekTheme)) +#define EEK_THEME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEK_TYPE_THEME, EekThemeClass)) +#define EEK_IS_THEME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEK_TYPE_THEME)) +#define EEK_IS_THEME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEK_TYPE_THEME)) +#define EEK_THEME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEK_TYPE_THEME, EekThemeClass)) + +GType eek_theme_get_type (void) G_GNUC_CONST; + +EekTheme *eek_theme_new (const char *application_stylesheet, + const char *theme_stylesheet, + const char *default_stylesheet); + +gboolean eek_theme_load_stylesheet (EekTheme *theme, + const char *path, + GError **error); + +void eek_theme_unload_stylesheet (EekTheme *theme, + const char *path); + +G_END_DECLS + +#endif /* __EEK_THEME_H__ */ diff --git a/eek/eek-types.c b/eek/eek-types.c index 9a1e9987..1522e3c3 100644 --- a/eek/eek-types.c +++ b/eek/eek-types.c @@ -222,3 +222,46 @@ eek_color_new (gdouble red, return color; } + +GType +eek_gradient_get_type (void) +{ + static GType our_type = 0; + + if (our_type == 0) + our_type = + g_boxed_type_register_static ("EekGradient", + (GBoxedCopyFunc)eek_gradient_copy, + (GBoxedFreeFunc)eek_gradient_free); + return our_type; +} + +EekGradient * +eek_gradient_new (EekGradientType type, + EekColor *start, + EekColor *stop) +{ + EekGradient *gradient; + + gradient = g_slice_new (EekGradient); + gradient->type = type; + gradient->start = eek_color_copy (start); + gradient->stop = eek_color_copy (stop); + + return gradient; +} + +EekGradient * +eek_gradient_copy (const EekGradient *gradient) +{ + return eek_gradient_new (gradient->type, gradient->start, gradient->stop); +} + +void +eek_gradient_free (EekGradient *gradient) +{ + if (gradient->start) + eek_color_free (gradient->start); + if (gradient->stop) + eek_color_free (gradient->stop); +} diff --git a/eek/eek-types.h b/eek/eek-types.h index 5c0b7e7a..fed7b350 100644 --- a/eek/eek-types.h +++ b/eek/eek-types.h @@ -136,6 +136,8 @@ typedef struct _EekSection EekSection; typedef struct _EekKeyboard EekKeyboard; typedef struct _EekSymbol EekSymbol; typedef struct _EekKeysym EekKeysym; +typedef struct _EekTheme EekTheme; +typedef struct _EekThemeNode EekThemeNode; typedef struct _EekSymbolMatrix EekSymbolMatrix; typedef struct _EekPoint EekPoint; @@ -253,5 +255,28 @@ EekColor *eek_color_new (gdouble red, EekColor *eek_color_copy (const EekColor *color); void eek_color_free (EekColor *color); +typedef enum { + EEK_GRADIENT_NONE, + EEK_GRADIENT_VERTICAL, + EEK_GRADIENT_HORIZONTAL, + EEK_GRADIENT_RADIAL +} EekGradientType; + +struct _EekGradient +{ + EekGradientType type; + EekColor *start; + EekColor *stop; +}; +typedef struct _EekGradient EekGradient; + +GType eek_gradient_get_type (void) G_GNUC_CONST; + +EekGradient *eek_gradient_new (EekGradientType type, + EekColor *start, + EekColor *stop); +EekGradient *eek_gradient_copy (const EekGradient *gradient); +void eek_gradient_free (EekGradient *gradient); + G_END_DECLS #endif /* EEK_TYPES_H */ diff --git a/eek/eek.h b/eek/eek.h index 6a768c82..20e997e9 100644 --- a/eek/eek.h +++ b/eek/eek.h @@ -28,5 +28,6 @@ #include "eek-keysym.h" #include "eek-xml.h" #include "eek-serializable.h" +#include "eek-theme.h" #endif /* EEK_H */ diff --git a/src/xml-main.c b/src/xml-main.c index 20d1b8f7..0f6a7530 100644 --- a/src/xml-main.c +++ b/src/xml-main.c @@ -47,6 +47,7 @@ static gchar *opt_layouts = NULL; static gchar *opt_options = NULL; static gchar *opt_list = NULL; static guint opt_group = 0; +static gchar *opt_theme = NULL; static const GOptionEntry options[] = { {"load", 'l', 0, G_OPTION_ARG_STRING, &opt_load, @@ -63,6 +64,8 @@ static const GOptionEntry options[] = { N_("Specify options")}, {"group", 'g', 0, G_OPTION_ARG_INT, &opt_group, N_("Specify group")}, + {"theme", 't', 0, G_OPTION_ARG_STRING, &opt_theme, + N_("Specify theme")}, {NULL} }; @@ -112,6 +115,8 @@ main (int argc, char **argv) } #endif + g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL); + context = g_option_context_new ("eek-example-xml"); g_option_context_add_main_entries (context, options, NULL); g_option_context_parse (context, &argc, &argv, NULL); @@ -151,6 +156,12 @@ main (int argc, char **argv) widget = gtk_clutter_embed_new (); stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED(widget)); actor = eek_clutter_keyboard_new (keyboard); + if (opt_theme) { + EekTheme *theme = eek_theme_new (opt_theme, NULL, NULL); + + eek_clutter_keyboard_set_theme (EEK_CLUTTER_KEYBOARD(actor), theme); + g_object_unref (theme); + } clutter_container_add_actor (CLUTTER_CONTAINER(stage), actor); clutter_stage_set_color (CLUTTER_STAGE(stage), &stage_color); @@ -164,6 +175,12 @@ main (int argc, char **argv) actor); #else widget = eek_gtk_keyboard_new (keyboard); + if (opt_theme) { + EekTheme *theme = eek_theme_new (opt_theme, NULL, NULL); + + eek_gtk_keyboard_set_theme (EEK_GTK_KEYBOARD(widget), theme); + g_object_unref (theme); + } #endif g_object_unref (keyboard);