From d291e568fde82c685c94781d559bc5552ac9bd0d Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Tue, 8 Sep 2009 15:47:30 -0400 Subject: [PATCH] Import Mx core as ST Import the core MxWidget/MxBin and their dependencies; we use the namespace "St" (Shell Toolkit) because it is the same length as Mx so enabling easy sharing of code, but makes it clear that this is a friendly fork and not a literal import. Based on a patch by Colin Walters https://bugzilla.gnome.org/show_bug.cgi?id=591245 --- configure.ac | 1 + src/Makefile-st.am | 102 +++ src/Makefile.am | 37 +- src/st/st-bin.c | 791 +++++++++++++++++++++ src/st/st-bin.h | 92 +++ src/st/st-enum-types.c.in | 30 + src/st/st-enum-types.h.in | 29 + src/st/st-marshal.list | 12 + src/st/st-private.c | 111 +++ src/st/st-private.h | 57 ++ src/st/st-stylable.c | 849 +++++++++++++++++++++++ src/st/st-stylable.h | 124 ++++ src/st/st-style.c | 780 +++++++++++++++++++++ src/st/st-style.h | 94 +++ src/st/st-subtexture.c | 575 ++++++++++++++++ src/st/st-subtexture.h | 96 +++ src/st/st-texture-cache.c | 450 ++++++++++++ src/st/st-texture-cache.h | 95 +++ src/st/st-texture-frame.c | 620 +++++++++++++++++ src/st/st-texture-frame.h | 93 +++ src/st/st-tooltip.c | 710 +++++++++++++++++++ src/st/st-tooltip.h | 80 +++ src/st/st-types.h | 73 ++ src/st/st-widget.c | 1357 +++++++++++++++++++++++++++++++++++++ src/st/st-widget.h | 108 +++ 25 files changed, 7360 insertions(+), 6 deletions(-) create mode 100644 src/Makefile-st.am create mode 100644 src/st/st-bin.c create mode 100644 src/st/st-bin.h create mode 100644 src/st/st-enum-types.c.in create mode 100644 src/st/st-enum-types.h.in create mode 100644 src/st/st-marshal.list create mode 100644 src/st/st-private.c create mode 100644 src/st/st-private.h create mode 100644 src/st/st-stylable.c create mode 100644 src/st/st-stylable.h create mode 100644 src/st/st-style.c create mode 100644 src/st/st-style.h create mode 100644 src/st/st-subtexture.c create mode 100644 src/st/st-subtexture.h create mode 100644 src/st/st-texture-cache.c create mode 100644 src/st/st-texture-cache.h create mode 100644 src/st/st-texture-frame.c create mode 100644 src/st/st-texture-frame.h create mode 100644 src/st/st-tooltip.c create mode 100644 src/st/st-tooltip.h create mode 100644 src/st/st-types.h create mode 100644 src/st/st-widget.c create mode 100644 src/st/st-widget.h diff --git a/configure.ac b/configure.ac index d9d261762..5c7efed63 100644 --- a/configure.ac +++ b/configure.ac @@ -57,6 +57,7 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-unix-2.0 gtk+-2.0 dbus-glib-1 mutter-plugin gnome-desktop-2.0 >= 2.26 libstartup-notification-1.0 gobject-introspection-1.0 >= 0.6.5) PKG_CHECK_MODULES(TIDY, clutter-1.0) +PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libccss-1 >= 0.3.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(TRAY, gtk+-2.0) diff --git a/src/Makefile-st.am b/src/Makefile-st.am new file mode 100644 index 000000000..24bc10c46 --- /dev/null +++ b/src/Makefile-st.am @@ -0,0 +1,102 @@ +st_cflags = \ + -I$(top_srcdir)/src \ + -DPREFIX=\""$(prefix)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DG_DISABLE_DEPRECATED \ + -DG_LOG_DOMAIN=\"St\" \ + -DST_COMPILATION \ + -DPACKAGE_DATA_DIR=\"$(pkgdatadir)\" \ + $(ST_CFLAGS) \ + $(NULL) + +st_built_sources = \ + st-enum-types.h \ + st-enum-types.c \ + st-marshal.h \ + st-marshal.c + +BUILT_SOURCES += $(st_built_sources) + +EXTRA_DIST += \ + st/st-marshal.list \ + st/st-enum-types.h.in \ + st/st-enum-types.c.in + +CLEANFILES += stamp-st-marshal.h stamp-st-enum-types.h + +st-marshal.h: stamp-st-marshal.h + @true +stamp-st-marshal.h: Makefile st/st-marshal.list + $(AM_V_GEN) $(GLIB_GENMARSHAL) \ + --prefix=_st_marshal \ + --header \ + $(srcdir)/st/st-marshal.list > $@.tmp && \ + (cmp -s $@.tmp st-marshal.h || cp -f $@.tmp st-marshal.h) && \ + rm -f $@.tmp && \ + echo timestamp > $(@F) + +st-marshal.c: Makefile st/st-marshal.list + $(AM_V_GEN) (echo "#include \"st-marshal.h\"" ; \ + $(GLIB_GENMARSHAL) \ + --prefix=_st_marshal \ + --body \ + $(srcdir)/st/st-marshal.list ) > $@.tmp && \ + cp -f $@.tmp st-marshal.c && \ + rm -f $@.tmp + +st-enum-types.h: stamp-st-enum-types.h Makefile + @true +stamp-st-enum-types.h: $(source_h) st/st-enum-types.h.in + $(AM_V_GEN) ( cd $(srcdir) && \ + $(GLIB_MKENUMS) \ + --template st/st-enum-types.h.in \ + $(st_source_h) ) >> $@.tmp && \ + (cmp -s $@.tmp st-enum-types.h || cp $@.tmp st-enum-types.h) && \ + rm -f $@.tmp && \ + echo timestamp > $(@F) + +st-enum-types.c: stamp-st-enum-types.h st/st-enum-types.c.in + $(AM_V_GEN) ( cd $(srcdir) && \ + $(GLIB_MKENUMS) \ + --template st/st-enum-types.c.in \ + $(st_source_h) ) >> $@.tmp && \ + cp $@.tmp $@ && \ + rm -f $@.tmp + +# please, keep this sorted alphabetically +st_source_h = \ + st/st-bin.h \ + st/st-private.h \ + st/st-stylable.h \ + st/st-style.h \ + st/st-subtexture.h \ + st/st-texture-cache.h \ + st/st-texture-frame.h \ + st/st-tooltip.h \ + st/st-types.h \ + st/st-widget.h \ + $(NULL) + +# please, keep this sorted alphabetically +st_source_c = \ + st/st-bin.c \ + st/st-private.c \ + st/st-stylable.c \ + st/st-style.c \ + st/st-subtexture.c \ + st/st-texture-cache.c \ + st/st-texture-frame.c \ + st/st-tooltip.c \ + st/st-widget.c \ + $(NULL) + +noinst_LTLIBRARIES += libst-1.0.la + +libst_1_0_la_LIBADD = $(ST_LIBS) +libst_1_0_la_SOURCES = \ + $(st_source_c) \ + $(st_source_h) \ + $(st_built_sources) \ + $(NULL) +libst_1_0_la_CPPFLAGS = $(st_cflags) +libst_1_0_la_LDFLAGS = $(LDADD) diff --git a/src/Makefile.am b/src/Makefile.am index fe8157946..04255cbb5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,6 +24,7 @@ EXTRA_DIST += gnome-shell.in include Makefile-big.am include Makefile-gdmuser.am +include Makefile-st.am include Makefile-tray.am gnome_shell_cflags = \ @@ -150,14 +151,15 @@ libgnome_shell_la_LIBADD = \ $(MUTTER_PLUGIN_LIBS) \ $(LIBGNOMEUI_LIBS) \ libbig-1.0.la \ + libst-1.0.la \ libgdmuser-1.0.la \ libtray.la libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) typelibdir = $(pkglibdir) -typelib_DATA = Shell-0.1.typelib Big-1.0.typelib +typelib_DATA = Shell-0.1.typelib Big-1.0.typelib St-1.0.typelib -Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir libgnome-shell.la Makefile +Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir St-1.0.gir libgnome-shell.la Makefile $(AM_V_GEN) $(G_IR_SCANNER) \ --namespace=Shell \ --nsversion=0.1 \ @@ -167,6 +169,7 @@ Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir libgnome-shell.la Makefile --libtool="$(LIBTOOL)" \ --add-include-path=$(builddir) \ --include=Big-1.0 \ + --include=St-1.0 \ --program=mutter \ --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \ $(addprefix $(srcdir)/,$(libgnome_shell_la_gir_sources)) \ @@ -177,14 +180,14 @@ CLEANFILES += Shell-0.1.gir # The dependency on libgnome-shell.la here is because g-ir-compiler opens it # (not the fake library, since we've already done the rewriting) Shell-0.1.typelib: libgnome-shell.la Shell-0.1.gir Big-1.0.gir - $(AM_V_GEN) LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH} \ + $(AM_V_GEN) \ $(G_IR_COMPILER) \ --includedir=. \ --includedir=$(MUTTER_LIB_DIR)/mutter/ \ Shell-0.1.gir -o $@ CLEANFILES += Shell-0.1.typelib -Big-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la $(srcdir)/big-enum-types.h Makefile +Big-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la Makefile $(AM_V_GEN) $(G_IR_SCANNER) \ --namespace=Big \ --nsversion=1.0 \ @@ -201,6 +204,28 @@ Big-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la $(srcdir) CLEANFILES += Big-1.0.gir Big-1.0.typelib: libbig-1.0.la Big-1.0.gir - $(AM_V_GEN) LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH} \ - $(G_IR_COMPILER) Big-1.0.gir -o $@ + $(AM_V_GEN) $(G_IR_COMPILER) Big-1.0.gir -o $@ CLEANFILES += Big-1.0.typelib + +St-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libst-1.0.la Makefile + $(AM_V_GEN) $(G_IR_SCANNER) \ + --namespace=St \ + --nsversion=1.0 \ + --include=Clutter-1.0 \ + --add-include-path=$(builddir) \ + --libtool="$(LIBTOOL)" \ + --program=mutter \ + --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \ + -DST_COMPILATION \ + $(addprefix $(srcdir)/,$(st_source_h)) \ + $(addprefix $(srcdir)/,$(st_source_c)) \ + $(srcdir)/st-enum-types.h \ + $(ST_CFLAGS) \ + -o $@ +CLEANFILES += St-1.0.gir + +St-1.0.typelib: St-1.0.gir + $(AM_V_GEN) $(G_IR_COMPILER) \ + $< -o $@ + +CLEANFILES += St-1.0.typelib diff --git a/src/st/st-bin.c b/src/st/st-bin.c new file mode 100644 index 000000000..005ed6bab --- /dev/null +++ b/src/st/st-bin.c @@ -0,0 +1,791 @@ +/* + * st-bin.c: Basic container actor + * + * Copyright (c) 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Emmanuele Bassi + * + */ + +/** + * SECTION:st-bin + * @short_description: a simple container with one actor + * + * #StBin is a simple container capable of having only one + * #ClutterActor as a child. + * + * #StBin inherits from #StWidget, so it is fully themable. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "st-bin.h" +#include "st-enum-types.h" +#include "st-private.h" +#include "st-stylable.h" + +#define ST_BIN_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_BIN, StBinPrivate)) + +struct _StBinPrivate +{ + ClutterActor *child; + + StAlign x_align; + StAlign y_align; + + guint x_fill : 1; + guint y_fill : 1; +}; + +enum +{ + PROP_0, + + PROP_CHILD, + PROP_X_ALIGN, + PROP_Y_ALIGN, + PROP_X_FILL, + PROP_Y_FILL +}; + +static void clutter_container_iface_init (ClutterContainerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (StBin, st_bin, ST_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, + clutter_container_iface_init)); + +void +_st_bin_get_align_factors (StBin *bin, + gdouble *x_align, + gdouble *y_align) +{ + StBinPrivate *priv = bin->priv; + gdouble factor; + + switch (priv->x_align) + { + case ST_ALIGN_START: + factor = 0.0; + break; + + case ST_ALIGN_MIDDLE: + factor = 0.5; + break; + + case ST_ALIGN_END: + factor = 1.0; + break; + + default: + factor = 0.0; + break; + } + + if (x_align) + *x_align = factor; + + switch (priv->y_align) + { + case ST_ALIGN_START: + factor = 0.0; + break; + + case ST_ALIGN_MIDDLE: + factor = 0.5; + break; + + case ST_ALIGN_END: + factor = 1.0; + break; + + default: + factor = 0.0; + break; + } + + if (y_align) + *y_align = factor; +} + +static void +st_bin_add (ClutterContainer *container, + ClutterActor *actor) +{ + st_bin_set_child (ST_BIN (container), actor); +} + +static void +st_bin_remove (ClutterContainer *container, + ClutterActor *actor) +{ + StBinPrivate *priv = ST_BIN (container)->priv; + + if (priv->child == actor) + st_bin_set_child (ST_BIN (container), NULL); +} + +static void +st_bin_foreach (ClutterContainer *container, + ClutterCallback callback, + gpointer user_data) +{ + StBinPrivate *priv = ST_BIN (container)->priv; + + if (priv->child) + callback (priv->child, user_data); +} + +static void +clutter_container_iface_init (ClutterContainerIface *iface) +{ + iface->add = st_bin_add; + iface->remove = st_bin_remove; + iface->foreach = st_bin_foreach; +} + +static void +st_bin_paint (ClutterActor *self) +{ + StBinPrivate *priv = ST_BIN (self)->priv; + + /* allow StWidget to paint the background */ + CLUTTER_ACTOR_CLASS (st_bin_parent_class)->paint (self); + + /* the pain our child */ + if (priv->child) + clutter_actor_paint (priv->child); +} + +static void +st_bin_pick (ClutterActor *self, + const ClutterColor *pick_color) +{ + StBinPrivate *priv = ST_BIN (self)->priv; + + /* get the default pick implementation */ + CLUTTER_ACTOR_CLASS (st_bin_parent_class)->pick (self, pick_color); + + if (priv->child) + clutter_actor_paint (priv->child); +} + +static void +st_bin_allocate (ClutterActor *self, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + StBinPrivate *priv = ST_BIN (self)->priv; + + CLUTTER_ACTOR_CLASS (st_bin_parent_class)->allocate (self, box, + flags); + + if (priv->child) + { + gfloat natural_width, natural_height; + gfloat min_width, min_height; + gfloat child_width, child_height; + gfloat available_width, available_height; + ClutterRequestMode request; + ClutterActorBox allocation = { 0, }; + StPadding padding = { 0, }; + gdouble x_align, y_align; + + _st_bin_get_align_factors (ST_BIN (self), &x_align, &y_align); + + st_widget_get_padding (ST_WIDGET (self), &padding); + + available_width = box->x2 - box->x1 + - padding.left - padding.right; + available_height = box->y2 - box->y1 + - padding.top - padding.bottom; + + if (available_width < 0) + available_width = 0; + + if (available_height < 0) + available_height = 0; + + if (priv->x_fill) + { + allocation.x1 = (int) padding.left; + allocation.x2 = (int)(allocation.x1 + available_width); + } + + if (priv->y_fill) + { + allocation.y1 = (int) padding.top; + allocation.y2 = (int)(allocation.y1 + available_height); + } + + /* if we are filling horizontally and vertically then we're done */ + if (priv->x_fill && priv->y_fill) + { + clutter_actor_allocate (priv->child, &allocation, flags); + return; + } + + request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH; + g_object_get (G_OBJECT (priv->child), "request-mode", &request, NULL); + + if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) + { + clutter_actor_get_preferred_width (priv->child, available_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + + clutter_actor_get_preferred_height (priv->child, child_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + } + else + { + clutter_actor_get_preferred_height (priv->child, available_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + + clutter_actor_get_preferred_width (priv->child, child_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + } + + if (!priv->x_fill) + { + allocation.x1 = (int)((available_width - child_width) * x_align + + padding.left); + allocation.x2 = allocation.x1 + child_width; + } + + if (!priv->y_fill) + { + allocation.y1 = (int)((available_height - child_height) * y_align + + padding.top); + allocation.y2 = allocation.y1 + child_height; + } + + clutter_actor_allocate (priv->child, &allocation, flags); + } +} + +static void +st_bin_get_preferred_width (ClutterActor *self, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + StBinPrivate *priv = ST_BIN (self)->priv; + gfloat min_width, natural_width; + StPadding padding = { 0, }; + + st_widget_get_padding (ST_WIDGET (self), &padding); + + min_width = natural_width = padding.left + padding.right; + + if (priv->child == NULL) + { + if (min_width_p) + *min_width_p = min_width; + + if (natural_width_p) + *natural_width_p = natural_width; + } + else + { + clutter_actor_get_preferred_width (priv->child, for_height, + min_width_p, + natural_width_p); + + if (min_width_p) + *min_width_p += min_width; + + if (natural_width_p) + *natural_width_p += natural_width; + } +} + +static void +st_bin_get_preferred_height (ClutterActor *self, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + StBinPrivate *priv = ST_BIN (self)->priv; + gfloat min_height, natural_height; + StPadding padding = { 0, }; + + st_widget_get_padding (ST_WIDGET (self), &padding); + + min_height = natural_height = padding.top + padding.bottom; + + if (priv->child == NULL) + { + if (min_height_p) + *min_height_p = min_height; + + if (natural_height_p) + *natural_height_p = natural_height; + } + else + { + clutter_actor_get_preferred_height (priv->child, for_width, + min_height_p, + natural_height_p); + + if (min_height_p) + *min_height_p += min_height; + + if (natural_height_p) + *natural_height_p += natural_height; + } +} + +static void +st_bin_dispose (GObject *gobject) +{ + StBinPrivate *priv = ST_BIN (gobject)->priv; + + if (priv->child) + { + clutter_actor_unparent (priv->child); + priv->child = NULL; + } + + G_OBJECT_CLASS (st_bin_parent_class)->dispose (gobject); +} + +static void +st_bin_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StBin *bin = ST_BIN (gobject); + + switch (prop_id) + { + case PROP_CHILD: + st_bin_set_child (bin, g_value_get_object (value)); + break; + + case PROP_X_ALIGN: + st_bin_set_alignment (bin, + g_value_get_enum (value), + bin->priv->y_align); + break; + + case PROP_Y_ALIGN: + st_bin_set_alignment (bin, + bin->priv->x_align, + g_value_get_enum (value)); + break; + + case PROP_X_FILL: + st_bin_set_fill (bin, + g_value_get_boolean (value), + bin->priv->y_fill); + break; + + case PROP_Y_FILL: + st_bin_set_fill (bin, + bin->priv->y_fill, + g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +st_bin_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StBinPrivate *priv = ST_BIN (gobject)->priv; + + switch (prop_id) + { + case PROP_CHILD: + g_value_set_object (value, priv->child); + break; + + case PROP_X_FILL: + g_value_set_boolean (value, priv->x_fill); + break; + + case PROP_Y_FILL: + g_value_set_boolean (value, priv->y_fill); + break; + + case PROP_X_ALIGN: + g_value_set_enum (value, priv->x_align); + break; + + case PROP_Y_ALIGN: + g_value_set_enum (value, priv->y_align); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +st_bin_class_init (StBinClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (StBinPrivate)); + + gobject_class->set_property = st_bin_set_property; + gobject_class->get_property = st_bin_get_property; + gobject_class->dispose = st_bin_dispose; + + actor_class->get_preferred_width = st_bin_get_preferred_width; + actor_class->get_preferred_height = st_bin_get_preferred_height; + actor_class->allocate = st_bin_allocate; + actor_class->paint = st_bin_paint; + actor_class->pick = st_bin_pick; + + /** + * StBin:child: + * + * The child #ClutterActor of the #StBin container. + */ + pspec = g_param_spec_object ("child", + "Child", + "The child of the Bin", + CLUTTER_TYPE_ACTOR, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CHILD, pspec); + + /** + * StBin:x-align: + * + * The horizontal alignment of the #StBin child. + */ + pspec = g_param_spec_enum ("x-align", + "X Align", + "The horizontal alignment", + ST_TYPE_ALIGN, + ST_ALIGN_MIDDLE, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_X_ALIGN, pspec); + + /** + * StBin:y-align: + * + * The vertical alignment of the #StBin child. + */ + pspec = g_param_spec_enum ("y-align", + "Y Align", + "The vertical alignment", + ST_TYPE_ALIGN, + ST_ALIGN_MIDDLE, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_Y_ALIGN, pspec); + + /** + * StBin:x-fill: + * + * Whether the child should fill the horizontal allocation + */ + pspec = g_param_spec_boolean ("x-fill", + "X Fill", + "Whether the child should fill the " + "horizontal allocation", + FALSE, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_X_FILL, pspec); + + /** + * StBin:y-fill: + * + * Whether the child should fill the vertical allocation + */ + pspec = g_param_spec_boolean ("y-fill", + "Y Fill", + "Whether the child should fill the " + "vertical allocation", + FALSE, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_Y_FILL, pspec); +} + +static void +st_bin_init (StBin *bin) +{ + bin->priv = ST_BIN_GET_PRIVATE (bin); + + bin->priv->x_align = ST_ALIGN_MIDDLE; + bin->priv->y_align = ST_ALIGN_MIDDLE; +} + +/** + * st_bin_new: + * + * Creates a new #StBin, a simple container for one child. + * + * Return value: the newly created #StBin actor + */ +StWidget * +st_bin_new (void) +{ + return g_object_new (ST_TYPE_BIN, NULL); +} + +/** + * st_bin_set_child: + * @bin: a #StBin + * @child: a #ClutterActor, or %NULL + * + * Sets @child as the child of @bin. + * + * If @bin already has a child, the previous child is removed. + */ +void +st_bin_set_child (StBin *bin, + ClutterActor *child) +{ + StBinPrivate *priv; + + g_return_if_fail (ST_IS_BIN (bin)); + g_return_if_fail (child == NULL || CLUTTER_IS_ACTOR (child)); + + priv = bin->priv; + + if (priv->child == child) + return; + + if (priv->child) + { + ClutterActor *old_child = priv->child; + + g_object_ref (old_child); + + priv->child = NULL; + clutter_actor_unparent (old_child); + + g_signal_emit_by_name (bin, "actor-removed", old_child); + + g_object_unref (old_child); + } + + if (child) + { + priv->child = child; + clutter_actor_set_parent (child, CLUTTER_ACTOR (bin)); + + g_signal_emit_by_name (bin, "actor-added", priv->child); + } + + clutter_actor_queue_relayout (CLUTTER_ACTOR (bin)); + + g_object_notify (G_OBJECT (bin), "child"); +} + +/** + * st_bin_get_child: + * @bin: a #StBin + * + * Retrieves a pointer to the child of @bin. + * + * Return value: a #ClutterActor, or %NULL + */ +ClutterActor * +st_bin_get_child (StBin *bin) +{ + g_return_val_if_fail (ST_IS_BIN (bin), NULL); + + return bin->priv->child; +} + +/** + * st_bin_set_alignment: + * @bin: a #StBin + * @x_align: horizontal alignment + * @y_align: vertical alignment + * + * Sets the horizontal and vertical alignment of the child + * inside a #StBin. + */ +void +st_bin_set_alignment (StBin *bin, + StAlign x_align, + StAlign y_align) +{ + StBinPrivate *priv; + gboolean changed = FALSE; + + g_return_if_fail (ST_IS_BIN (bin)); + + priv = bin->priv; + + g_object_freeze_notify (G_OBJECT (bin)); + + if (priv->x_align != x_align) + { + priv->x_align = x_align; + g_object_notify (G_OBJECT (bin), "x-align"); + changed = TRUE; + } + + if (priv->y_align != y_align) + { + priv->y_align = y_align; + g_object_notify (G_OBJECT (bin), "y-align"); + changed = TRUE; + } + + if (changed) + clutter_actor_queue_relayout (CLUTTER_ACTOR (bin)); + + g_object_thaw_notify (G_OBJECT (bin)); +} + +/** + * st_bin_get_alignment: + * @bin: a #StBin + * @x_align: return location for the horizontal alignment, or %NULL + * @y_align: return location for the vertical alignment, or %NULL + * + * Retrieves the horizontal and vertical alignment of the child + * inside a #StBin, as set by st_bin_set_alignment(). + */ +void +st_bin_get_alignment (StBin *bin, + StAlign *x_align, + StAlign *y_align) +{ + StBinPrivate *priv; + + g_return_if_fail (ST_IS_BIN (bin)); + + priv = bin->priv; + + if (x_align) + *x_align = priv->x_align; + + if (y_align) + *y_align = priv->y_align; +} + +/** + * st_bin_set_fill: + * @bin: a #StBin + * @x_fill: %TRUE if the child should fill horizontally the @bin + * @y_fill: %TRUE if the child should fill vertically the @bin + * + * Sets whether the child of @bin should fill out the horizontal + * and/or vertical allocation of the parent + */ +void +st_bin_set_fill (StBin *bin, + gboolean x_fill, + gboolean y_fill) +{ + StBinPrivate *priv; + gboolean changed = FALSE; + + g_return_if_fail (ST_IS_BIN (bin)); + + priv = bin->priv; + + g_object_freeze_notify (G_OBJECT (bin)); + + if (priv->x_fill != x_fill) + { + priv->x_fill = x_fill; + changed = TRUE; + + g_object_notify (G_OBJECT (bin), "x-fill"); + } + + if (priv->y_fill != y_fill) + { + priv->y_fill = y_fill; + changed = TRUE; + + g_object_notify (G_OBJECT (bin), "y-fill"); + } + + if (changed) + clutter_actor_queue_relayout (CLUTTER_ACTOR (bin)); + + g_object_thaw_notify (G_OBJECT (bin)); +} + +/** + * st_bin_get_fill: + * @bin: a #StBin + * @x_fill: (out): return location for the horizontal fill, or %NULL + * @y_fill: (out): return location for the vertical fill, or %NULL + * + * Retrieves the horizontal and vertical fill settings + */ +void +st_bin_get_fill (StBin *bin, + gboolean *x_fill, + gboolean *y_fill) +{ + g_return_if_fail (ST_IS_BIN (bin)); + + if (x_fill) + *x_fill = bin->priv->x_fill; + + if (y_fill) + *y_fill = bin->priv->y_fill; +} + +static gpointer +st_padding_copy (gpointer data) +{ + return g_slice_dup (StPadding, data); +} + +static void +st_padding_free (gpointer data) +{ + if (G_LIKELY (data)) + g_slice_free (StPadding, data); +} + +GType +st_padding_get_type (void) +{ + static GType our_type = 0; + + if (G_UNLIKELY (our_type == 0)) + our_type = g_boxed_type_register_static (I_("StPadding"), + st_padding_copy, + st_padding_free); + + return our_type; +} diff --git a/src/st/st-bin.h b/src/st/st-bin.h new file mode 100644 index 000000000..68fed603c --- /dev/null +++ b/src/st/st-bin.h @@ -0,0 +1,92 @@ +/* + * st-bin.h: Basic container actor + * + * Copyright 2009, 2008 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_BIN_H__ +#define __ST_BIN_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_BIN (st_bin_get_type ()) +#define ST_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_BIN, StBin)) +#define ST_IS_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_BIN)) +#define ST_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_BIN, StBinClass)) +#define ST_IS_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_BIN)) +#define ST_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_BIN, StBinClass)) + +typedef struct _StBin StBin; +typedef struct _StBinPrivate StBinPrivate; +typedef struct _StBinClass StBinClass; + +/** + * StBin: + * + * The #StBin struct contains only private data + */ +struct _StBin +{ + /*< private >*/ + StWidget parent_instance; + + StBinPrivate *priv; +}; + +/** + * StBinClass: + * + * The #StBinClass struct contains only private data + */ +struct _StBinClass +{ + /*< private >*/ + StWidgetClass parent_class; +}; + +GType st_bin_get_type (void) G_GNUC_CONST; + +StWidget * st_bin_new (void); +void st_bin_set_child (StBin *bin, + ClutterActor *child); +ClutterActor *st_bin_get_child (StBin *bin); +void st_bin_set_alignment (StBin *bin, + StAlign x_align, + StAlign y_align); +void st_bin_get_alignment (StBin *bin, + StAlign *x_align, + StAlign *y_align); +void st_bin_set_fill (StBin *bin, + gboolean x_fill, + gboolean y_fill); +void st_bin_get_fill (StBin *bin, + gboolean *x_fill, + gboolean *y_fill); + +G_END_DECLS + +#endif /* __ST_BIN_H__ */ diff --git a/src/st/st-enum-types.c.in b/src/st/st-enum-types.c.in new file mode 100644 index 000000000..738ac2769 --- /dev/null +++ b/src/st/st-enum-types.c.in @@ -0,0 +1,30 @@ +/*** BEGIN file-header ***/ +#include "st-enum-types.h" +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +#include "@filename@" +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type(void) { + static GType enum_type_id = 0; + if (G_UNLIKELY (!enum_type_id)) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + enum_type_id = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + return enum_type_id; +} +/*** END value-tail ***/ diff --git a/src/st/st-enum-types.h.in b/src/st/st-enum-types.h.in new file mode 100644 index 000000000..b3dd0e74e --- /dev/null +++ b/src/st/st-enum-types.h.in @@ -0,0 +1,29 @@ +/*** BEGIN file-header ***/ +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_ENUM_TYPES_H__ +#define __ST_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* !__ST_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define ST_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) + +/*** END value-header ***/ diff --git a/src/st/st-marshal.list b/src/st/st-marshal.list new file mode 100644 index 000000000..9b3bed671 --- /dev/null +++ b/src/st/st-marshal.list @@ -0,0 +1,12 @@ +VOID:OBJECT +VOID:VOID +VOID:PARAM +VOID:POINTER +VOID:UINT +VOID:UINT,UINT +VOID:OBJECT,OBJECT +VOID:STRING,OBJECT +VOID:OBJECT,OBJECT,INT,INT +VOID:OBJECT,FLOAT,FLOAT,INT,ENUM +VOID:FLOAT,FLOAT,INT,ENUM +VOID:FLOAT,FLOAT diff --git a/src/st/st-private.c b/src/st/st-private.c new file mode 100644 index 000000000..0a35e4efa --- /dev/null +++ b/src/st/st-private.c @@ -0,0 +1,111 @@ +#include "st-private.h" + +/* Utility function to modify a child allocation box with respect to the + * x/y-fill child properties. Expects childbox to contain the available + * allocation space. + */ +void +_st_allocate_fill (ClutterActor *child, + ClutterActorBox *childbox, + StAlign x_alignment, + StAlign y_alignment, + gboolean x_fill, + gboolean y_fill) +{ + gfloat natural_width, natural_height; + gfloat min_width, min_height; + gfloat child_width, child_height; + gfloat available_width, available_height; + ClutterRequestMode request; + ClutterActorBox allocation = { 0, }; + gdouble x_align, y_align; + + if (x_alignment == ST_ALIGN_START) + x_align = 0.0; + else if (x_alignment == ST_ALIGN_MIDDLE) + x_align = 0.5; + else + x_align = 1.0; + + if (y_alignment == ST_ALIGN_START) + y_align = 0.0; + else if (y_alignment == ST_ALIGN_MIDDLE) + y_align = 0.5; + else + y_align = 1.0; + + available_width = childbox->x2 - childbox->x1; + available_height = childbox->y2 - childbox->y1; + + if (available_width < 0) + available_width = 0; + + if (available_height < 0) + available_height = 0; + + if (x_fill) + { + allocation.x1 = childbox->x1; + allocation.x2 = (int)(allocation.x1 + available_width); + } + + if (y_fill) + { + allocation.y1 = childbox->y1; + allocation.y2 = (int)(allocation.y1 + available_height); + } + + /* if we are filling horizontally and vertically then we're done */ + if (x_fill && y_fill) + { + *childbox = allocation; + return; + } + + request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH; + g_object_get (G_OBJECT (child), "request-mode", &request, NULL); + + if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) + { + clutter_actor_get_preferred_width (child, available_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + + clutter_actor_get_preferred_height (child, child_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + } + else + { + clutter_actor_get_preferred_height (child, available_width, + &min_height, + &natural_height); + + child_height = CLAMP (natural_height, min_height, available_height); + + clutter_actor_get_preferred_width (child, child_height, + &min_width, + &natural_width); + + child_width = CLAMP (natural_width, min_width, available_width); + } + + if (!x_fill) + { + allocation.x1 = childbox->x1 + (int)((available_width - child_width) * x_align); + allocation.x2 = allocation.x1 + (int) child_width; + } + + if (!y_fill) + { + allocation.y1 = childbox->y1 + (int)((available_height - child_height) * y_align); + allocation.y2 = allocation.y1 + (int) child_height; + } + + *childbox = allocation; + +} diff --git a/src/st/st-private.h b/src/st/st-private.h new file mode 100644 index 000000000..1e99cf695 --- /dev/null +++ b/src/st/st-private.h @@ -0,0 +1,57 @@ +/* + * st-private.h: Private declarations + * + * Copyright 2007 OpenedHand + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef __ST_PRIVATE_H__ +#define __ST_PRIVATE_H__ + +#include +#include "st-widget.h" +#include "st-bin.h" + +G_BEGIN_DECLS + +#define I_(str) (g_intern_static_string ((str))) + +#define ST_PARAM_READABLE \ + (G_PARAM_READABLE | \ + G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB) + +#define ST_PARAM_READWRITE \ + (G_PARAM_READABLE | G_PARAM_WRITABLE | \ + G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB) + +G_END_DECLS + +ClutterActor *_st_widget_get_dnd_clone (StWidget *widget); + +void _st_bin_get_align_factors (StBin *bin, + gdouble *x_align, + gdouble *y_align); + +void _st_allocate_fill (ClutterActor *child, + ClutterActorBox *childbox, + StAlign x_align, + StAlign y_align, + gboolean x_fill, + gboolean y_fill); + +#endif /* __ST_PRIVATE_H__ */ diff --git a/src/st/st-stylable.c b/src/st/st-stylable.c new file mode 100644 index 000000000..1af8788a5 --- /dev/null +++ b/src/st/st-stylable.c @@ -0,0 +1,849 @@ +/* + * st-stylable.c: Interface for stylable objects + * + * Copyright 2008 Intel Corporation + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: Emmanuele Bassi + * Thomas Wood + * + */ + +/** + * SECTION:st-stylable + * @short_description: Interface for stylable objects + * + * Stylable objects are classes that can have "style properties", that is + * properties that can be changed by attaching a #StStyle to them. + * + * Objects can choose to subclass #StWidget, and thus inherit all the + * #StWidget style properties; or they can subclass #StWidget and + * reimplement the #StStylable interface to add new style properties + * specific for them (and their subclasses); or, finally, they can simply + * subclass #GObject and implement #StStylable to install new properties. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include + +#include "st-marshal.h" +#include "st-private.h" +#include "st-stylable.h" + +enum +{ + STYLE_CHANGED, + STYLE_NOTIFY, + CHANGED, + + LAST_SIGNAL +}; + +static GObjectNotifyContext property_notify_context = { 0, }; + +static GParamSpecPool *style_property_spec_pool = NULL; + +static GQuark quark_real_owner = 0; +static GQuark quark_style = 0; + +static guint stylable_signals[LAST_SIGNAL] = { 0, }; + +static void +st_stylable_notify_dispatcher (GObject *gobject, + guint n_pspecs, + GParamSpec **pspecs) +{ + guint i; + + for (i = 0; i < n_pspecs; i++) + g_signal_emit (gobject, stylable_signals[STYLE_NOTIFY], + g_quark_from_string (pspecs[i]->name), + pspecs[i]); +} + +static void +st_stylable_base_finalize (gpointer g_iface) +{ + GList *list, *node; + + list = g_param_spec_pool_list_owned (style_property_spec_pool, + G_TYPE_FROM_INTERFACE (g_iface)); + + for (node = list; node; node = node->next) + { + GParamSpec *pspec = node->data; + + g_param_spec_pool_remove (style_property_spec_pool, pspec); + g_param_spec_unref (pspec); + } + + g_list_free (list); +} + +static void +st_stylable_base_init (gpointer g_iface) +{ + static gboolean initialised = FALSE; + + if (G_UNLIKELY (!initialised)) + { + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + + initialised = TRUE; + + quark_real_owner = g_quark_from_static_string ("st-stylable-real-owner-quark"); + quark_style = g_quark_from_static_string ("st-stylable-style-quark"); + + style_property_spec_pool = g_param_spec_pool_new (FALSE); + + property_notify_context.quark_notify_queue = g_quark_from_static_string ("StStylable-style-property-notify-queue"); + property_notify_context.dispatcher = st_stylable_notify_dispatcher; + + /** + * StStylable:style: + * + * The #StStyle attached to a stylable object. + */ + g_object_interface_install_property (g_iface, + g_param_spec_object ("style", + "Style", + "A style object", + ST_TYPE_STYLE, + ST_PARAM_READWRITE)); + + /** + * StStylable::style-changed: + * @stylable: the #StStylable that received the signal + * @old_style: the previously set #StStyle for @stylable + * + * The ::style-changed signal is emitted each time one of the style + * properties have changed. + */ + stylable_signals[STYLE_CHANGED] = + g_signal_new (I_("style-changed"), + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (StStylableIface, style_changed), + NULL, NULL, + _st_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * StStylable::stylable-changed: + * @actor: the actor that received the signal + * + * The ::changed signal is emitted each time any of the properties of the + * stylable has changed. + */ + stylable_signals[CHANGED] = + g_signal_new (I_("stylable-changed"), + iface_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StStylableIface, stylable_changed), + NULL, NULL, + _st_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + stylable_signals[STYLE_NOTIFY] = + g_signal_new (I_("style-notify"), + iface_type, + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (StStylableIface, style_notify), + NULL, NULL, + _st_marshal_VOID__PARAM, + G_TYPE_NONE, 1, + G_TYPE_PARAM); + } +} + +GType +st_stylable_get_type (void) +{ + static GType our_type = 0; + + if (G_UNLIKELY (our_type == 0)) + { + GTypeInfo stylable_info = { + sizeof (StStylableIface), + st_stylable_base_init, + st_stylable_base_finalize + }; + + our_type = g_type_register_static (G_TYPE_INTERFACE, + I_("StStylable"), + &stylable_info, 0); + } + + return our_type; +} + +void +st_stylable_freeze_notify (StStylable *stylable) +{ + g_return_if_fail (ST_IS_STYLABLE (stylable)); + + g_object_ref (stylable); + g_object_notify_queue_freeze (G_OBJECT (stylable), &property_notify_context); + g_object_unref (stylable); +} + +void +st_stylable_thaw_notify (StStylable *stylable) +{ + GObjectNotifyQueue *nqueue; + + g_return_if_fail (ST_IS_STYLABLE (stylable)); + + g_object_ref (stylable); + + nqueue = g_object_notify_queue_from_object (G_OBJECT (stylable), + &property_notify_context); + + if (!nqueue || !nqueue->freeze_count) + g_warning ("%s: property-changed notification for %s(%p) is not frozen", + G_STRFUNC, G_OBJECT_TYPE_NAME (stylable), stylable); + else + g_object_notify_queue_thaw (G_OBJECT (stylable), nqueue); + + g_object_unref (stylable); +} + +void +st_stylable_notify (StStylable *stylable, + const gchar *property_name) +{ + GParamSpec *pspec; + + g_return_if_fail (ST_IS_STYLABLE (stylable)); + g_return_if_fail (property_name != NULL); + + g_object_ref (stylable); + + pspec = g_param_spec_pool_lookup (style_property_spec_pool, + property_name, + G_OBJECT_TYPE (stylable), + TRUE); + + if (!pspec) + g_warning ("%s: object class `%s' has no style property named `%s'", + G_STRFUNC, + G_OBJECT_TYPE_NAME (stylable), + property_name); + else + { + GObjectNotifyQueue *nqueue; + + nqueue = g_object_notify_queue_freeze (G_OBJECT (stylable), + &property_notify_context); + g_object_notify_queue_add (G_OBJECT (stylable), nqueue, pspec); + g_object_notify_queue_thaw (G_OBJECT (stylable), nqueue); + } + + g_object_unref (stylable); +} + +/** + * st_stylable_iface_install_property: + * @iface: a #StStylableIface + * @owner_type: #GType of the style property owner + * @pspec: a #GParamSpec + * + * Installs a property for @owner_type using @pspec as the property + * description. + * + * This function should be used inside the #StStylableIface initialization + * function of a class, for instance: + * + * + * G_DEFINE_TYPE_WITH_CODE (FooActor, foo_actor, CLUTTER_TYPE_ACTOR, + * G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE, + * st_stylable_init)); + * ... + * static void + * st_stylable_init (StStylableIface *iface) + * { + * static gboolean is_initialized = FALSE; + * + * if (!is_initialized) + * { + * ... + * st_stylable_iface_install_property (stylable, + * FOO_TYPE_ACTOR, + * g_param_spec_int ("x-spacing", + * "X Spacing", + * "Horizontal spacing", + * -1, G_MAXINT, + * 2, + * G_PARAM_READWRITE)); + * ... + * } + * } + * + */ +void +st_stylable_iface_install_property (StStylableIface *iface, + GType owner_type, + GParamSpec *pspec) +{ + g_return_if_fail (ST_IS_STYLABLE_IFACE (iface)); + g_return_if_fail (owner_type != G_TYPE_INVALID); + g_return_if_fail (G_IS_PARAM_SPEC (pspec)); + g_return_if_fail (pspec->flags & G_PARAM_READABLE); + g_return_if_fail (!(pspec->flags & (G_PARAM_CONSTRUCT_ONLY | G_PARAM_CONSTRUCT + ))); + + if (g_param_spec_pool_lookup (style_property_spec_pool, pspec->name, + owner_type, + FALSE)) + { + g_warning ("%s: class `%s' already contains a style property named `%s'", + G_STRLOC, + g_type_name (owner_type), + pspec->name); + return; + } + + g_param_spec_ref_sink (pspec); + g_param_spec_set_qdata_full (pspec, quark_real_owner, + g_strdup (g_type_name (owner_type)), + g_free); + + g_param_spec_pool_insert (style_property_spec_pool, + pspec, + owner_type); +} + +/** + * st_stylable_list_properties: + * @stylable: a #StStylable + * @n_props: return location for the number of properties, or %NULL + * + * Retrieves all the #GParamSpecs installed by @stylable. + * + * Return value: an array of #GParamSpecs. Free it with + * g_free() when done. + */ +GParamSpec ** +st_stylable_list_properties (StStylable *stylable, + guint *n_props) +{ + GParamSpec **pspecs = NULL; + guint n; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + pspecs = g_param_spec_pool_list (style_property_spec_pool, + G_OBJECT_TYPE (stylable), + &n); + if (n_props) + *n_props = n; + + return pspecs; +} + +/** + * st_stylable_find_property: + * @stylable: a #StStylable + * @property_name: the name of the property to find + * + * Finds the #GParamSpec installed by @stylable for the property + * with @property_name. + * + * Return value: a #GParamSpec for the given property, or %NULL if + * no property with that name was found + */ +GParamSpec * +st_stylable_find_property (StStylable *stylable, + const gchar *property_name) +{ + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + return g_param_spec_pool_lookup (style_property_spec_pool, + property_name, + G_OBJECT_TYPE (stylable), + TRUE); +} + +static inline void +st_stylable_get_property_internal (StStylable *stylable, + GParamSpec *pspec, + GValue *value) +{ + StStyle *style; + GValue real_value = { 0, }; + + style = st_stylable_get_style (stylable); + + if (!style) + { + g_value_reset (value); + return; + } + + st_style_get_property (style, stylable, pspec, &real_value); + + g_value_copy (&real_value, value); + g_value_unset (&real_value); + +} + +/** + * st_stylable_get_property: + * @stylable: a #StStylable + * @property_name: the name of the property + * @value: return location for an empty #GValue + * + * Retrieves the value of @property_name for @stylable, and puts it + * into @value. + */ +void +st_stylable_get_property (StStylable *stylable, + const gchar *property_name, + GValue *value) +{ + GParamSpec *pspec; + + g_return_if_fail (ST_IS_STYLABLE (stylable)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (value != NULL); + + pspec = st_stylable_find_property (stylable, property_name); + if (!pspec) + { + g_warning ("Stylable class `%s' doesn't have a property named `%s'", + g_type_name (G_OBJECT_TYPE (stylable)), + property_name); + return; + } + + if (!(pspec->flags & G_PARAM_READABLE)) + { + g_warning ("Style property `%s' of class `%s' is not readable", + pspec->name, + g_type_name (G_OBJECT_TYPE (stylable))); + return; + } + + if (G_VALUE_TYPE (value) != G_PARAM_SPEC_VALUE_TYPE (pspec)) + { + g_warning ("Passed value is not of the requested type `%s' for " + "the style property `%s' of class `%s'", + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)), + pspec->name, + g_type_name (G_OBJECT_TYPE (stylable))); + return; + } + + st_stylable_get_property_internal (stylable, pspec, value); +} + +/** + * st_stylable_get: + * @stylable: a #StStylable + * @first_property_name: name of the first property to get + * @Varargs: return location for the first property, followed optionally + * by more name/return location pairs, followed by %NULL + * + * Gets the style properties for @stylable. + * + * In general, a copy is made of the property contents and the called + * is responsible for freeing the memory in the appropriate manner for + * the property type. + * + * + * Using st_stylable_get(<!-- -->) + * An example of using st_stylable_get() to get the contents of + * two style properties - one of type #G_TYPE_INT and one of type + * #CLUTTER_TYPE_COLOR: + * + * gint x_spacing; + * ClutterColor *bg_color; + * + * st_stylable_get (stylable, + * "x-spacing", &x_spacing, + * "bg-color", &bg_color, + * NULL); + * + * /* do something with x_spacing and bg_color */ + * + * clutter_color_free (bg_color); + * + * + */ +void +st_stylable_get (StStylable *stylable, + const gchar *first_property_name, + ...) +{ + StStyle *style; + va_list args; + + g_return_if_fail (ST_IS_STYLABLE (stylable)); + g_return_if_fail (first_property_name != NULL); + + style = st_stylable_get_style (stylable); + + va_start (args, first_property_name); + st_style_get_valist (style, stylable, first_property_name, args); + va_end (args); +} + +/** + * st_stylable_get_default_value: + * @stylable: a #StStylable + * @property_name: name of the property to query + * @value_out: return location for the default value + * + * Query @stylable for the default value of property @property_name and + * fill @value_out with the result. + * + * Returns: %TRUE if property @property_name exists and the default value has + * been returned. + */ +gboolean +st_stylable_get_default_value (StStylable *stylable, + const gchar *property_name, + GValue *value_out) +{ + GParamSpec *pspec; + + pspec = st_stylable_find_property (stylable, property_name); + if (!pspec) + { + g_warning ("%s: no style property named `%s' found for class `%s'", + G_STRLOC, + property_name, + g_type_name (G_OBJECT_TYPE (stylable))); + return FALSE; + } + + if (!(pspec->flags & G_PARAM_READABLE)) + { + g_warning ("Style property `%s' of class `%s' is not readable", + pspec->name, + g_type_name (G_OBJECT_TYPE (stylable))); + return FALSE; + } + + g_value_init (value_out, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_param_value_set_default (pspec, value_out); + return TRUE; +} + +/** + * st_stylable_get_style: + * @stylable: a #StStylable + * + * Retrieves the #StStyle used by @stylable. This function does not + * alter the reference count of the returned object. + * + * Return value: a #StStyle + */ +StStyle * +st_stylable_get_style (StStylable *stylable) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + if (iface->get_style) + return iface->get_style (stylable); + + return g_object_get_data (G_OBJECT (stylable), "st-stylable-style"); +} + +/** + * st_stylable_set_style: + * @stylable: a #StStylable + * @style: a #StStyle + * + * Sets @style as the new #StStyle to be used by @stylable. + * + * The #StStylable will take ownership of the passed #StStyle. + * + * After the #StStle has been set, the StStylable::style-set signal + * will be emitted. + */ +void +st_stylable_set_style (StStylable *stylable, + StStyle *style) +{ + StStylableIface *iface; + StStyle *old_style; + + g_return_if_fail (ST_IS_STYLABLE (stylable)); + g_return_if_fail (ST_IS_STYLE (style)); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + old_style = st_stylable_get_style (stylable); + g_object_ref (old_style); + + if (iface->set_style) + iface->set_style (stylable, style); + else + { + g_object_set_qdata_full (G_OBJECT (stylable), + quark_style, + g_object_ref_sink (style), + g_object_unref); + } + + g_signal_emit (stylable, stylable_signals[STYLE_CHANGED], 0, old_style); + g_object_unref (old_style); + + g_object_notify (G_OBJECT (stylable), "style"); +} + +/** + * st_stylable_get_container: + * @stylable: a #StStylable + * + * Obtain the parent #StStylable that contains @stylable. + * + * Return value: The parent #StStylable + */ +StStylable* +st_stylable_get_container (StStylable *stylable) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + if (iface->get_container) + return iface->get_container (stylable); + else + return NULL; +} + +/** + * st_stylable_get_base_style: + * @stylable: a #StStylable + * + * Get the parent ancestor #StStylable of @stylable. + * + * Return value: the parent #StStylable + */ +StStylable* +st_stylable_get_base_style (StStylable *stylable) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + if (iface->get_base_style) + return iface->get_base_style (stylable); + else + return NULL; +} + + +/** + * st_stylable_get_style_id: + * @stylable: a #StStylable + * + * Get the ID value of @stylable + * + * Return value: the id of @stylable + */ +const gchar* +st_stylable_get_style_id (StStylable *stylable) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + if (iface->get_style_id) + return iface->get_style_id (stylable); + else + return NULL; +} + +/** + * st_stylable_get_style_type: + * @stylable: a #StStylable + * + * Get the type name of @stylable + * + * Return value: the type name of @stylable + */ +const gchar* +st_stylable_get_style_type (StStylable *stylable) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + if (iface->get_style_type) + return iface->get_style_type (stylable); + else + return G_OBJECT_TYPE_NAME (stylable); +} + +/** + * st_stylable_get_style_class: + * @stylable: a #StStylable + * + * Get the style class name of @stylable + * + * Return value: the type name of @stylable + */ +const gchar* +st_stylable_get_style_class (StStylable *stylable) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + if (iface->get_style_class) + return iface->get_style_class (stylable); + else + return NULL; +} + +/** + * st_stylable_get_pseudo_class: + * @stylable: a #StStylable + * + * Get the pseudo class name of @stylable + * + * Return value: the pseudo class name of @stylable + */ +const gchar* +st_stylable_get_pseudo_class (StStylable *stylable) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + if (iface->get_pseudo_class) + return iface->get_pseudo_class (stylable); + else + return NULL; +} + +/** + * st_stylable_get_attribute: + * @stylable: a #StStylable + * @name: attribute name + * + * Get the named attribute from @stylable + * + * Return value: the value of the attribute + */ +gchar* +st_stylable_get_attribute (StStylable *stylable, + const gchar *name) +{ + StStylableIface *iface; + GValue value = { 0, }; + GValue string_value = { 0, }; + gchar *ret; + GParamSpec *pspec; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), NULL); + + iface = ST_STYLABLE_GET_IFACE (stylable); + + if (iface->get_attribute) + return iface->get_attribute (stylable, name); + + /* look up a generic gobject property */ + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (stylable), name); + + /* if no such property exists, return NULL */ + if (pspec == NULL) + return NULL; + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_object_get_property (G_OBJECT (stylable), name, &value); + + g_value_init (&string_value, G_TYPE_STRING); + if (g_value_transform (&value, &string_value)) + ret = g_strdup (g_value_get_string (&string_value)); + else + ret = NULL; + + g_value_unset (&value); + g_value_unset (&string_value); + + return ret; +} + +/** + * st_stylable_get_viewport: + * @stylable: a #StStylable + * @x: location to store X coordinate + * @y: location to store Y coordinate + * @width: location to store width + * @height: location to store height + * + * Obtain the position and dimensions of @stylable. + * + * Return value: true if the function succeeded + */ +gboolean +st_stylable_get_viewport (StStylable *stylable, + gint *x, + gint *y, + gint *width, + gint *height) +{ + StStylableIface *iface; + + g_return_val_if_fail (ST_IS_STYLABLE (stylable), FALSE); + + iface = ST_STYLABLE_GET_IFACE (stylable); + if (iface->get_viewport) + return iface->get_viewport (stylable, x, y, width, height); + else + return FALSE; +} + + +/** + * st_stylable_changed: + * @stylable: A #StStylable + * + * Emit the "stylable-changed" signal on @stylable + */ +void +st_stylable_changed (StStylable *stylable) +{ + g_signal_emit (stylable, stylable_signals[CHANGED], 0, NULL); +} diff --git a/src/st/st-stylable.h b/src/st/st-stylable.h new file mode 100644 index 000000000..b00a2ba97 --- /dev/null +++ b/src/st/st-stylable.h @@ -0,0 +1,124 @@ +/* + * st-stylable.h: Interface for stylable objects + * + * Copyright 2008, 2009 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + * Thomas Wood + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_STYLABLE_H__ +#define __ST_STYLABLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_STYLABLE (st_stylable_get_type ()) +#define ST_STYLABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_STYLABLE, StStylable)) +#define ST_IS_STYLABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_STYLABLE)) +#define ST_STYLABLE_IFACE(iface) (G_TYPE_CHECK_CLASS_CAST ((iface), ST_TYPE_STYLABLE, StStylableIface)) +#define ST_IS_STYLABLE_IFACE(iface) (G_TYPE_CHECK_CLASS_TYPE ((iface), ST_TYPE_STYLABLE)) +#define ST_STYLABLE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), ST_TYPE_STYLABLE, StStylableIface)) + +/* StStylableIface is defined in st-style.h */ + +struct _StStylableIface +{ + GTypeInterface g_iface; + + /* virtual functions */ + StStyle * (* get_style) (StStylable *stylable); + void (* set_style) (StStylable *stylable, + StStyle *style); + + /* context virtual functions */ + StStylable *(*get_container) (StStylable *stylable); + StStylable *(*get_base_style) (StStylable *stylable); + const gchar *(*get_style_id) (StStylable *stylable); + const gchar *(*get_style_type) (StStylable *stylable); + const gchar *(*get_style_class) (StStylable *stylable); + const gchar *(*get_pseudo_class) (StStylable *stylable); + gchar *(*get_attribute) (StStylable *stylable, + const gchar *name); + gboolean (*get_viewport) (StStylable *stylable, + gint *x, + gint *y, + gint *width, + gint *height); + + /* signals, not vfuncs */ + void (* style_notify) (StStylable *stylable, + GParamSpec *pspec); + void (* style_changed) (StStylable *stylable); + + void (* stylable_changed) (StStylable *stylable); +}; + +GType st_stylable_get_type (void) G_GNUC_CONST; + +void st_stylable_iface_install_property (StStylableIface *iface, + GType owner_type, + GParamSpec *pspec); + +void st_stylable_freeze_notify (StStylable *stylable); +void st_stylable_notify (StStylable *stylable, + const gchar *property_name); +void st_stylable_thaw_notify (StStylable *stylable); +GParamSpec **st_stylable_list_properties (StStylable *stylable, + guint *n_props); +GParamSpec * st_stylable_find_property (StStylable *stylable, + const gchar *property_name); +void st_stylable_set_style (StStylable *stylable, + StStyle *style); +StStyle * st_stylable_get_style (StStylable *stylable); + +void st_stylable_get (StStylable *stylable, + const gchar *first_property_name, + ...) G_GNUC_NULL_TERMINATED; +void st_stylable_get_property (StStylable *stylable, + const gchar *property_name, + GValue *value); +gboolean st_stylable_get_default_value (StStylable *stylable, + const gchar *property_name, + GValue *value_out); + +StStylable* st_stylable_get_container (StStylable *stylable); +StStylable* st_stylable_get_base_style (StStylable *stylable); +const gchar* st_stylable_get_style_id (StStylable *stylable); +const gchar* st_stylable_get_style_type (StStylable *stylable); +const gchar* st_stylable_get_style_class (StStylable *stylable); +const gchar* st_stylable_get_pseudo_class (StStylable *stylable); +gchar* st_stylable_get_attribute (StStylable *stylable, + const gchar *name); +gboolean st_stylable_get_viewport (StStylable *stylable, + gint *x, + gint *y, + gint *width, + gint *height); + +void st_stylable_changed (StStylable *stylable); + +G_END_DECLS + +#endif /* __ST_STYLABLE_H__ */ diff --git a/src/st/st-style.c b/src/st/st-style.c new file mode 100644 index 000000000..15e74a675 --- /dev/null +++ b/src/st/st-style.c @@ -0,0 +1,780 @@ +/* + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/** + * SECTION:st-style + * @short_description: a data store for style properties + * + * #StStyle is a property data store that can read properties from a style + * sheet. It is queried with objects that implement the StStylable + * interface. + */ + + +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +#include "st-stylable.h" +#include "st-style.h" +#include "st-types.h" +#include "st-marshal.h" +#include "st-widget.h" + +enum +{ + CHANGED, + + LAST_SIGNAL +}; + +#define ST_STYLE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_STYLE, StStylePrivate)) + +#define ST_STYLE_ERROR g_style_error_quark () + +typedef struct { + GType value_type; + gchar *value_name; + GValue value; +} StyleProperty; + +struct _StStylePrivate +{ + ccss_stylesheet_t *stylesheet; + GList *image_paths; + + GHashTable *style_hash; + GHashTable *node_hash; +}; + +typedef struct { + ccss_node_t parent; + StStylable *stylable; + StStylableIface *iface; +} st_style_node_t; + +static ccss_function_t const * peek_css_functions (void); + +static ccss_node_class_t * peek_node_class (void); + +static guint style_signals[LAST_SIGNAL] = { 0, }; + +static StStyle *default_style = NULL; + +G_DEFINE_TYPE (StStyle, st_style, G_TYPE_OBJECT); + +static GQuark +g_style_error_quark (void) +{ + return g_quark_from_static_string ("st-style-error-quark"); +} + +static gboolean +st_style_real_load_from_file (StStyle *style, + const gchar *filename, + GError **error, + gint priority) +{ + StStylePrivate *priv; + ccss_grammar_t *grammar; + GError *internal_error; + gchar *path; + GList *l; + + g_return_val_if_fail (ST_IS_STYLE (style), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + priv = ST_STYLE (style)->priv; + + if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR)) + { + internal_error = g_error_new (ST_STYLE_ERROR, + ST_STYLE_ERROR_INVALID_FILE, + _("Invalid theme file '%s'"), filename); + g_propagate_error (error, internal_error); + return FALSE; + } + + + /* add the path of the stylesheet to the search path */ + path = g_path_get_dirname (filename); + + /* make sure path is valid */ + if (!path) + return TRUE; + + for (l = priv->image_paths; l; l = l->next) + { + if (g_str_equal ((gchar *) l->data, path)) + { + /* we have this path already */ + g_free (path); + path = NULL; + } + } + + /* Add the new path */ + if (path) + priv->image_paths = g_list_append (priv->image_paths, path); + + /* now load the stylesheet */ + if (!priv->stylesheet) + { + grammar = ccss_grammar_create_css (); + ccss_grammar_add_functions (grammar, peek_css_functions ()); + priv->stylesheet = ccss_grammar_create_stylesheet_from_file (grammar, + filename, + path); + ccss_grammar_destroy (grammar); + } + else + { + ccss_stylesheet_add_from_file (priv->stylesheet, filename, priority, path); + } + + g_signal_emit (style, style_signals[CHANGED], 0, NULL); + + return TRUE; +} + +/** + * st_style_load_from_file: + * @style: a #StStyle + * @filename: filename of the style sheet to load + * @error: a #GError or #NULL + * + * Load style information from the specified file. + * + * returns: TRUE if the style information was loaded successfully. Returns + * FALSE on error. + */ +gboolean +st_style_load_from_file (StStyle *style, + const gchar *filename, + GError **error) +{ + return st_style_real_load_from_file (style, filename, error, + CCSS_STYLESHEET_AUTHOR); +} + +static void +st_style_load (StStyle *style) +{ + const gchar *env_var; + gchar *rc_file = NULL; + GError *error; + + env_var = g_getenv ("ST_RC_FILE"); + if (env_var && *env_var) + rc_file = g_strdup (env_var); + + if (!rc_file) + rc_file = g_build_filename (PACKAGE_DATA_DIR, + "st", + "style", + "default.css", + NULL); + + error = NULL; + + if (g_file_test (rc_file, G_FILE_TEST_EXISTS)) + { + /* load the default theme with lowest priority */ + if (!st_style_real_load_from_file (style, rc_file, &error, CCSS_STYLESHEET_USER_AGENT)) + { + g_critical ("Unable to load resource file '%s': %s", + rc_file, + error->message); + g_error_free (error); + } + } + + g_free (rc_file); +} + +static void +st_style_finalize (GObject *gobject) +{ + StStylePrivate *priv = ((StStyle *) gobject)->priv; + GList *l; + + for (l = priv->image_paths; l; l = g_list_delete_link (l, l)) + { + g_free (l->data); + } + + G_OBJECT_CLASS (st_style_parent_class)->finalize (gobject); +} + +static void +st_style_class_init (StStyleClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (StStylePrivate)); + + gobject_class->finalize = st_style_finalize; + + /** + * StStyle::changed: + * + * Indicates that the style data has changed in some way. For example, a new + * stylesheet may have been loaded. + */ + + style_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StStyleClass, changed), + NULL, NULL, + _st_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/* url loader for libccss */ +static char * +ccss_url (GSList const *args, + void *user_data) +{ + const gchar *given_path, *filename; + gchar *test_path; + + g_return_val_if_fail (args, NULL); + + given_path = (char const *) args->data; + + /* we can only deal with local paths */ + if (!g_str_has_prefix (given_path, "file://")) + return NULL; + filename = &given_path[7]; + + /* + * Handle absolute paths correctly + */ + if (*filename == '/') + return strdup (filename); + + /* first try looking in the theme dir */ + test_path = g_build_filename (g_get_user_config_dir (), + "st", + filename, + NULL); + if (g_file_test (test_path, G_FILE_TEST_IS_REGULAR)) + return test_path; + g_free (test_path); + + if (user_data) + { + test_path = g_build_filename ((gchar *) user_data, filename, NULL); + + if (g_file_test (test_path, G_FILE_TEST_IS_REGULAR)) + return test_path; + + g_free (test_path); + } + else + { + g_warning ("No path available css url resolver!"); + } + + /* couldn't find the image anywhere, so just return the filename */ + return strdup (given_path); +} + +static ccss_function_t const * +peek_css_functions (void) +{ + static ccss_function_t const ccss_functions[] = + { + { "url", ccss_url }, + { NULL } + }; + + return ccss_functions; +} + + +static void +st_style_init (StStyle *style) +{ + StStylePrivate *priv; + + style->priv = priv = ST_STYLE_GET_PRIVATE (style); + + /* create a hash table to look up pointer keys and values */ + style->priv->node_hash = g_hash_table_new_full (NULL, NULL, + NULL, g_free); + style->priv->style_hash = g_hash_table_new_full (NULL, NULL, + NULL, (GDestroyNotify) ccss_style_destroy); + + st_style_load (style); +} + +/** + * st_style_new: + * + * Creates a new #StStyle object. This must be freed using #g_object_unref + * when no longer required. + * + * Returns: a newly allocated #StStyle + */ +StStyle * +st_style_new (void) +{ + return g_object_new (ST_TYPE_STYLE, NULL); +} + +/** + * st_style_get_default: + * + * Return the default StStyle object. This includes the current theme (if + * any). + * + * Returns: a #StStyle object. This must not be freed or unref'd by + * applications + */ +StStyle * +st_style_get_default (void) +{ + if (G_LIKELY (default_style)) + return default_style; + + default_style = g_object_new (ST_TYPE_STYLE, NULL); + + return default_style; +} + +/* functions for ccss */ + +static st_style_node_t * +get_container (st_style_node_t *node) +{ + st_style_node_t *container; + ClutterActor *parent; + + g_return_val_if_fail (node, NULL); + g_return_val_if_fail (node->iface, NULL); + g_return_val_if_fail (node->stylable, NULL); + + parent = clutter_actor_get_parent (CLUTTER_ACTOR (node->stylable)); + while (parent && !ST_IS_WIDGET (parent)) + parent = clutter_actor_get_parent (CLUTTER_ACTOR (parent)); + + if (!parent) + return NULL; + + container = g_new0 (st_style_node_t, 1); + ccss_node_init ((ccss_node_t*) container, peek_node_class ()); + container->iface = node->iface; + container->stylable = ST_STYLABLE (parent); + + return container; +} + +static const gchar* +get_style_id (st_style_node_t *node) +{ + return st_stylable_get_style_id (node->stylable); +} + +static const gchar* +get_style_type (st_style_node_t *node) +{ + return st_stylable_get_style_type (node->stylable); +} + +static const gchar* +get_style_class (st_style_node_t *node) +{ + return st_stylable_get_style_class (node->stylable); +} + +static const gchar* +get_pseudo_class (st_style_node_t *node) +{ + return st_stylable_get_pseudo_class (node->stylable); +} + +static const gchar* +get_attribute (st_style_node_t *node, + const char *name) +{ + return st_stylable_get_attribute (node->stylable, name); +} + +static void +release (st_style_node_t *node) +{ + g_return_if_fail (node); + + g_free (node); +} + +static ccss_node_class_t * +peek_node_class (void) +{ + static ccss_node_class_t _node_class = { + .is_a = NULL, + .get_container = (ccss_node_get_container_f) get_container, + .get_id = (ccss_node_get_id_f) get_style_id, + .get_type = (ccss_node_get_type_f) get_style_type, + .get_class = (ccss_node_get_class_f) get_style_class, + .get_pseudo_class = (ccss_node_get_pseudo_class_f) get_pseudo_class, + .get_viewport = NULL, // (ccss_node_get_viewport_f) get_viewport, + .get_attribute = (ccss_node_get_attribute_f) get_attribute, + .release = (ccss_node_release_f) release + }; + + return &_node_class; +} + +static void +st_style_fetch_ccss_property (ccss_style_t *ccss_style, + GParamSpec *pspec, + GValue *value) +{ + gboolean value_set = FALSE; + + g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + + if (G_PARAM_SPEC_VALUE_TYPE (pspec)) + { + double number; + + if (G_IS_PARAM_SPEC_INT (pspec)) + { + if (ccss_style_get_double (ccss_style, pspec->name, &number)) + { + g_value_set_int (value, (gint) number); + value_set = TRUE; + } + } + else if (G_IS_PARAM_SPEC_UINT (pspec)) + { + if (ccss_style_get_double (ccss_style, pspec->name, &number)) + { + g_value_set_uint (value, (guint) number); + value_set = TRUE; + } + } + else if (G_PARAM_SPEC_VALUE_TYPE (pspec) == ST_TYPE_BORDER_IMAGE && + !g_strcmp0 ("border-image", pspec->name)) + { + ccss_border_image_t const *border_image; + + if (ccss_style_get_property (ccss_style, + "border-image", + (ccss_property_base_t const **) &border_image)) + { + if (border_image && + border_image->base.state == CCSS_PROPERTY_STATE_SET) + { + g_value_set_boxed (value, border_image); + value_set = TRUE; + } + } + } + else if (ST_TYPE_PADDING == G_PARAM_SPEC_VALUE_TYPE (pspec) && + 0 == g_strcmp0 ("padding", pspec->name)) + { + StPadding padding = { 0, }; + gboolean padding_set = 0; + + if (ccss_style_get_double (ccss_style, "padding-top", &number)) + { + padding.top = number; + padding_set = TRUE; + } + + if (ccss_style_get_double (ccss_style, "padding-right", &number)) + { + padding.right = number; + padding_set = TRUE; + } + + if (ccss_style_get_double (ccss_style, "padding-bottom", &number)) + { + padding.bottom = number; + padding_set = TRUE; + } + + if (ccss_style_get_double (ccss_style, "padding-left", &number)) + { + padding.left = number; + padding_set = TRUE; + } + + if (padding_set) + { + g_value_set_boxed (value, &padding); + value_set = TRUE; + } + } + else + { + gchar *string = NULL; + + ccss_style_get_string (ccss_style, pspec->name, &string); + + if (string) + { + if (CLUTTER_IS_PARAM_SPEC_COLOR (pspec)) + { + ClutterColor color = { 0, }; + + clutter_color_from_string (&color, string); + clutter_value_set_color (value, &color); + + value_set = TRUE; + } + else + if (G_IS_PARAM_SPEC_STRING (pspec)) + { + g_value_set_string (value, string); + value_set = TRUE; + } + g_free (string); + } + } + } + + /* no value was found in css, so copy in the default value */ + if (!value_set) + g_param_value_set_default (pspec, value); +} + +static ccss_style_t* +st_style_get_ccss_query (StStyle *style, + StStylable *stylable) +{ + StStylableIface *iface = ST_STYLABLE_GET_IFACE (stylable); + ccss_style_t *ccss_style; + st_style_node_t *ccss_node; + + ccss_node = g_hash_table_lookup (style->priv->node_hash, stylable); + + if (!ccss_node) + { + ccss_node = g_new0 (st_style_node_t, 1); + ccss_node_init ((ccss_node_t*) ccss_node, peek_node_class ()); + ccss_node->iface = iface; + ccss_node->stylable = stylable; + + g_hash_table_insert (style->priv->node_hash, stylable, ccss_node); + g_signal_connect_swapped (stylable, "stylable-changed", + G_CALLBACK (g_hash_table_remove), + style->priv->node_hash); + + + g_object_weak_ref ((GObject*) stylable, + (GWeakNotify) g_hash_table_remove, style->priv->node_hash); + } + + + ccss_style = g_hash_table_lookup (style->priv->style_hash, stylable); + + if (!ccss_style) + { + ccss_style = ccss_stylesheet_query (style->priv->stylesheet, + (ccss_node_t *) ccss_node); + + g_hash_table_insert (style->priv->style_hash, stylable, ccss_style); + + /* remove the cache if the stylable changes */ + g_signal_connect_swapped (stylable, "stylable-changed", + G_CALLBACK (g_hash_table_remove), + style->priv->style_hash); + + g_object_weak_ref ((GObject*) stylable, + (GWeakNotify) g_hash_table_remove, style->priv->style_hash); + } + + return ccss_style; + +} + + +/** + * st_style_get_property: + * @style: the style data store object + * @stylable: a stylable to retreive the data for + * @pspec: a #GParamSpec describing the property required + * @value: a #GValue to place the return value in + * + * Requests the property described in @pspec for the specified stylable + */ + +void +st_style_get_property (StStyle *style, + StStylable *stylable, + GParamSpec *pspec, + GValue *value) +{ + StStylePrivate *priv; + gboolean value_set = FALSE; + + g_return_if_fail (ST_IS_STYLE (style)); + g_return_if_fail (ST_IS_STYLABLE (stylable)); + g_return_if_fail (pspec != NULL); + g_return_if_fail (value != NULL); + + priv = style->priv; + + /* look up the property in the css */ + if (priv->stylesheet) + { + ccss_style_t *ccss_style; + + ccss_style = st_style_get_ccss_query (style, stylable); + if (ccss_style) + { + st_style_fetch_ccss_property (ccss_style, pspec, value); + value_set = TRUE; + } + } + + /* no value was found in css, so copy in the default value */ + if (!value_set) + { + g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_param_value_set_default (pspec, value); + } +} + +/** + * st_style_get_valist: + * @style: a #StStyle + * @stylable: a #StStylable + * @first_property_name: name of the first property to get + * @va_args: return location for the first property, followed optionally + * by more name/return location pairs, followed by %NULL + * + * Gets the style properties for @stylable from @style. + * + * Please refer to st_style_get() for further information. + */ +void +st_style_get_valist (StStyle *style, + StStylable *stylable, + const gchar *first_property_name, + va_list va_args) +{ + StStylePrivate *priv; + const gchar *name = first_property_name; + gboolean values_set = FALSE; + + g_return_if_fail (ST_IS_STYLE (style)); + g_return_if_fail (ST_IS_STYLABLE (stylable)); + g_return_if_fail (style->priv != NULL); + + priv = style->priv; + + /* look up the property in the css */ + if (priv->stylesheet) + { + ccss_style_t *ccss_style; + + ccss_style = st_style_get_ccss_query (style, stylable); + + if (ccss_style) + { + while (name) + { + GValue value = { 0, }; + gchar *error = NULL; + GParamSpec *pspec = st_stylable_find_property (stylable, name); + st_style_fetch_ccss_property (ccss_style, pspec, &value); + G_VALUE_LCOPY (&value, va_args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + g_value_unset (&value); + break; + } + g_value_unset (&value); + name = va_arg (va_args, gchar*); + } + values_set = TRUE; + } + } + + if (!values_set) + { + /* Set the remaining properties to their default values + * even if broken out of the above loop. */ + while (name) + { + GValue value = { 0, }; + gchar *error = NULL; + st_stylable_get_default_value (stylable, name, &value); + G_VALUE_LCOPY (&value, va_args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + g_value_unset (&value); + break; + } + g_value_unset (&value); + name = va_arg (va_args, gchar*); + } + } +} + +/** + * st_style_get: + * @style: a #StStyle + * @stylable: a #StStylable + * @first_property_name: name of the first property to get + * @Varargs: return location for the first property, followed optionally + * by more name/return location pairs, followed by %NULL + * + * Gets the style properties for @stylable from @style. + * + * In general, a copy is made of the property contents and the caller + * is responsible for freeing the memory in the appropriate manner for + * the property type. + */ +void +st_style_get (StStyle *style, + StStylable *stylable, + const gchar *first_property_name, + ...) +{ + va_list va_args; + + g_return_if_fail (ST_IS_STYLE (style)); + g_return_if_fail (first_property_name != NULL); + + va_start (va_args, first_property_name); + st_style_get_valist (style, stylable, first_property_name, va_args); + va_end (va_args); +} + diff --git a/src/st/st-style.h b/src/st/st-style.h new file mode 100644 index 000000000..ce63a2a92 --- /dev/null +++ b/src/st/st-style.h @@ -0,0 +1,94 @@ +/* + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_STYLE_H__ +#define __ST_STYLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_STYLE (st_style_get_type ()) +#define ST_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_STYLE, StStyle)) +#define ST_IS_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_STYLE)) +#define ST_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_STYLE, StStyleClass)) +#define ST_IS_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_STYLE)) +#define ST_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_STYLE, StStyleClass)) + +typedef struct _StStyle StStyle; +typedef struct _StStylePrivate StStylePrivate; +typedef struct _StStyleClass StStyleClass; + +/* forward declaration */ +typedef struct _StStylable StStylable; /* dummy typedef */ +typedef struct _StStylableIface StStylableIface; + +typedef enum { /*< prefix=ST_STYLE_ERROR >*/ + ST_STYLE_ERROR_INVALID_FILE +} StStyleError; + +/** + * StStyle: + * + * The contents of this structure is private and should only be accessed using + * the provided API. + */ +struct _StStyle +{ + /*< private >*/ + GObject parent_instance; + + StStylePrivate *priv; +}; + +struct _StStyleClass +{ + GObjectClass parent_class; + + void (* changed) (StStyle *style); +}; + +GType st_style_get_type (void) G_GNUC_CONST; + +StStyle *st_style_get_default (void); +StStyle *st_style_new (void); + +gboolean st_style_load_from_file (StStyle *style, + const gchar *filename, + GError **error); +void st_style_get_property (StStyle *style, + StStylable *stylable, + GParamSpec *pspec, + GValue *value); +void st_style_get (StStyle *style, + StStylable *stylable, + const gchar *first_property_name, + ...) G_GNUC_NULL_TERMINATED; +void st_style_get_valist (StStyle *style, + StStylable *stylable, + const gchar *first_property_name, + va_list va_args); + +G_END_DECLS + +#endif /* __ST_STYLE_H__ */ diff --git a/src/st/st-subtexture.c b/src/st/st-subtexture.c new file mode 100644 index 000000000..7ea1fa4b6 --- /dev/null +++ b/src/st/st-subtexture.c @@ -0,0 +1,575 @@ +/* + * st-subtexture.h: Class to wrap a texture and "subframe" it. + * based on + * st-texture-frame.c: Expandible texture actor + * + * Copyright 2007 OpenedHand + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "st-subtexture.h" + +enum +{ + PROP_0, + + PROP_PARENT_TEXTURE, + + PROP_TOP, + PROP_LEFT, + PROP_WIDTH, + PROP_HEIGHT +}; + +G_DEFINE_TYPE (StSubtexture, st_subtexture, CLUTTER_TYPE_ACTOR); + +#define ST_SUBTEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_SUBTEXTURE, StSubtexturePrivate)) + +struct _StSubtexturePrivate +{ + ClutterTexture *parent_texture; + + int left; + int top; + int width; + int height; + + CoglHandle material; +}; + +static void +st_subtexture_get_preferred_width (ClutterActor *self, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv; + + if (G_UNLIKELY (priv->parent_texture == NULL)) + { + if (min_width_p) + *min_width_p = 0; + + if (natural_width_p) + *natural_width_p = 0; + } + else + { + if (min_width_p) + *min_width_p = priv->width; + if (natural_width_p) + *natural_width_p = priv->width; + } +} + +static void +st_subtexture_get_preferred_height (ClutterActor *self, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv; + + if (G_UNLIKELY (priv->parent_texture == NULL)) + { + if (min_height_p) + *min_height_p = 0; + + if (natural_height_p) + *natural_height_p = 0; + } + else + { + if (min_height_p) + *min_height_p = priv->height; + if (natural_height_p) + *natural_height_p = priv->height; + } +} + +static void +st_subtexture_realize (ClutterActor *self) +{ + StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv; + + if (priv->material != COGL_INVALID_HANDLE) + return; + + priv->material = cogl_material_new (); + + CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_REALIZED); +} + +static void +st_subtexture_unrealize (ClutterActor *self) +{ + StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv; + + if (priv->material == COGL_INVALID_HANDLE) + return; + + cogl_material_unref (priv->material); + priv->material = COGL_INVALID_HANDLE; + + CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED); +} + +static void +st_subtexture_paint (ClutterActor *self) +{ + StSubtexturePrivate *priv = ST_SUBTEXTURE (self)->priv; + CoglHandle cogl_texture = COGL_INVALID_HANDLE; + ClutterActorBox box = { 0, 0, 0, 0 }; + gfloat tx1, ty1, tx2, ty2, tex_width, tex_height, width, height; + guint8 opacity; + + /* no need to paint stuff if we don't have a texture */ + if (G_UNLIKELY (priv->parent_texture == NULL)) + return; + + /* parent texture may have been hidden, so need to make sure it gets + * realized + */ + if (!CLUTTER_ACTOR_IS_REALIZED (priv->parent_texture)) + clutter_actor_realize (CLUTTER_ACTOR (priv->parent_texture)); + + cogl_texture = clutter_texture_get_cogl_texture (priv->parent_texture); + if (cogl_texture == COGL_INVALID_HANDLE) + return; + + tex_width = cogl_texture_get_width (cogl_texture); + tex_height = cogl_texture_get_height (cogl_texture); + + clutter_actor_get_allocation_box (self, &box); + + width = box.x2 - box.x1; + height = box.y2 - box.y1; + + tx1 = 1.0 * priv->left / tex_width; + ty1 = 1.0 * priv->top / tex_height; + + tx2 = 1.0 * (priv->left + priv->width) / tex_width; + ty2 = 1.0 * (priv->top + priv->height) / tex_height; + + + opacity = clutter_actor_get_paint_opacity (self); + + g_assert (priv->material != COGL_INVALID_HANDLE); + + /* set the source material using the parent texture's COGL handle */ + cogl_material_set_color4ub (priv->material, 255, 255, 255, opacity); + cogl_material_set_layer (priv->material, 0, cogl_texture); + cogl_set_source (priv->material); + + cogl_rectangle_with_texture_coords (0,0, (float) width, (float) height, + tx1, ty1, tx2, ty2); +} + +static inline void +st_subtexture_set_frame_internal (StSubtexture *frame, + int left, + int top, + int width, + int height) +{ + StSubtexturePrivate *priv = frame->priv; + GObject *gobject = G_OBJECT (frame); + gboolean changed = FALSE; + + g_object_freeze_notify (gobject); + + if (priv->top != top) + { + priv->top = top; + g_object_notify (gobject, "top"); + changed = TRUE; + } + + if (priv->left != left) + { + priv->left = left; + g_object_notify (gobject, "left"); + changed = TRUE; + } + + if (priv->width != width) + { + priv->width = width; + g_object_notify (gobject, "width"); + changed = TRUE; + } + + if (priv->height != height) + { + priv->height = height; + g_object_notify (gobject, "height"); + changed = TRUE; + } + + if (changed && CLUTTER_ACTOR_IS_VISIBLE (frame)) + clutter_actor_queue_redraw (CLUTTER_ACTOR (frame)); + + g_object_thaw_notify (gobject); +} + +static void +st_subtexture_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StSubtexture *frame = ST_SUBTEXTURE (gobject); + StSubtexturePrivate *priv = frame->priv; + + switch (prop_id) + { + case PROP_PARENT_TEXTURE: + st_subtexture_set_parent_texture (frame, + g_value_get_object (value)); + break; + + case PROP_TOP: + st_subtexture_set_frame_internal (frame, + priv->left, + g_value_get_int (value), + priv->width, + priv->height); + break; + + case PROP_LEFT: + st_subtexture_set_frame_internal (frame, + g_value_get_int (value), + priv->top, + priv->width, + priv->height); + break; + + case PROP_WIDTH: + st_subtexture_set_frame_internal (frame, + priv->left, + priv->top, + g_value_get_int (value), + priv->height); + break; + + case PROP_HEIGHT: + st_subtexture_set_frame_internal (frame, + priv->left, + priv->top, + priv->width, + g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_subtexture_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StSubtexturePrivate *priv = ST_SUBTEXTURE (gobject)->priv; + + switch (prop_id) + { + case PROP_PARENT_TEXTURE: + g_value_set_object (value, priv->parent_texture); + break; + + case PROP_LEFT: + g_value_set_int (value, priv->left); + break; + + case PROP_TOP: + g_value_set_int (value, priv->top); + break; + + case PROP_WIDTH: + g_value_set_int (value, priv->width); + break; + + case PROP_HEIGHT: + g_value_set_int (value, priv->height); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_subtexture_dispose (GObject *gobject) +{ + StSubtexturePrivate *priv = ST_SUBTEXTURE (gobject)->priv; + + if (priv->parent_texture) + { + g_object_unref (priv->parent_texture); + priv->parent_texture = NULL; + } + + if (priv->material) + { + cogl_material_unref (priv->material); + priv->material = COGL_INVALID_HANDLE; + } + + G_OBJECT_CLASS (st_subtexture_parent_class)->dispose (gobject); +} + +static void +st_subtexture_class_init (StSubtextureClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (gobject_class, sizeof (StSubtexturePrivate)); + + actor_class->get_preferred_width = + st_subtexture_get_preferred_width; + actor_class->get_preferred_height = + st_subtexture_get_preferred_height; + actor_class->realize = st_subtexture_realize; + actor_class->unrealize = st_subtexture_unrealize; + actor_class->paint = st_subtexture_paint; + + gobject_class->set_property = st_subtexture_set_property; + gobject_class->get_property = st_subtexture_get_property; + gobject_class->dispose = st_subtexture_dispose; + + pspec = g_param_spec_object ("parent-texture", + "Parent Texture", + "The parent ClutterTexture", + CLUTTER_TYPE_TEXTURE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT); + g_object_class_install_property (gobject_class, PROP_PARENT_TEXTURE, pspec); + + pspec = g_param_spec_int ("left", + "Left", + "Left offset", + 0, G_MAXINT, + 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_LEFT, pspec); + + pspec = g_param_spec_int ("top", + "Top", + "Top offset", + 0, G_MAXINT, + 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_TOP, pspec); + + pspec = g_param_spec_int ("width", + "Width", + "Width", + 0, G_MAXINT, + 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_WIDTH, pspec); + + pspec = g_param_spec_int ("height", + "Height", + "Height", + 0, G_MAXINT, + 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_HEIGHT, pspec); +} + +static void +st_subtexture_init (StSubtexture *self) +{ + StSubtexturePrivate *priv; + + self->priv = priv = ST_SUBTEXTURE_GET_PRIVATE (self); + + priv->material = COGL_INVALID_HANDLE; +} + +/** + * st_subtexture_new: + * @texture: a #ClutterTexture or %NULL + * @left: left + * @top: top + * @width: width + * @height: height + * + * A #StSubtexture is a specialized texture that efficiently clones + * an area of the given @texture while keeping preserving portions of the + * same texture. + * + * A #StSubtexture can be used to make a rectangular texture fit a + * given size without stretching its borders. + * + * Return value: the newly created #StSubtexture + */ +ClutterActor* +st_subtexture_new (ClutterTexture *texture, + gint left, + gint top, + gint width, + gint height) +{ + g_return_val_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture), NULL); + + return g_object_new (ST_TYPE_SUBTEXTURE, + "parent-texture", texture, + "top", top, + "left", left, + "width", width, + "height", height, + NULL); +} + +/** + * st_subtexture_get_parent_texture: + * @frame: A #StSubtexture + * + * Return the texture used by the #StSubtexture + * + * Returns: a #ClutterTexture owned by the #StSubtexture + */ +ClutterTexture * +st_subtexture_get_parent_texture (StSubtexture *frame) +{ + g_return_val_if_fail (ST_IS_SUBTEXTURE (frame), NULL); + + return frame->priv->parent_texture; +} + +/** + * st_subtexture_set_parent_texture: + * @frame: A #StSubtexture + * @texture: A #ClutterTexture + * + * Set the #ClutterTexture used by this #StSubtexture + * + */ +void +st_subtexture_set_parent_texture (StSubtexture *frame, + ClutterTexture *texture) +{ + StSubtexturePrivate *priv; + gboolean was_visible; + + g_return_if_fail (ST_IS_SUBTEXTURE (frame)); + g_return_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture)); + + priv = frame->priv; + + was_visible = CLUTTER_ACTOR_IS_VISIBLE (frame); + + if (priv->parent_texture == texture) + return; + + if (priv->parent_texture) + { + g_object_unref (priv->parent_texture); + priv->parent_texture = NULL; + + if (was_visible) + clutter_actor_hide (CLUTTER_ACTOR (frame)); + } + + if (texture) + { + priv->parent_texture = g_object_ref (texture); + + if (was_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->parent_texture)) + clutter_actor_show (CLUTTER_ACTOR (frame)); + } + + clutter_actor_queue_relayout (CLUTTER_ACTOR (frame)); + + g_object_notify (G_OBJECT (frame), "parent-texture"); +} + +/** + * st_subtexture_set_frame: + * @frame: A #StSubtexture + * @left: left + * @top: top + * @width: width + * @height: height + * + * Set the frame of the subtexture + * + */ +void +st_subtexture_set_frame (StSubtexture *frame, + gint left, + gint top, + gint width, + gint height) +{ + g_return_if_fail (ST_IS_SUBTEXTURE (frame)); + + st_subtexture_set_frame_internal (frame, left, top, width, height); +} + +/** + * st_subtexture_get_frame: + * @frame: A #StSubtexture + * @left: left + * @top: top + * @width: width + * @height: height + * + * Retrieve the current frame. + * + */ +void +st_subtexture_get_frame (StSubtexture *frame, + gint *left, + gint *top, + gint *width, + gint *height) +{ + StSubtexturePrivate *priv; + + g_return_if_fail (ST_IS_SUBTEXTURE (frame)); + + priv = frame->priv; + + if (top) + *top = priv->top; + + if (left) + *left = priv->left; + + if (width) + *width = priv->width; + + if (height) + *height = priv->height; +} diff --git a/src/st/st-subtexture.h b/src/st/st-subtexture.h new file mode 100644 index 000000000..ce9c10eab --- /dev/null +++ b/src/st/st-subtexture.h @@ -0,0 +1,96 @@ +/* + * st-subtexture.h: Class to wrap a texture and "subframe" it. + * + * Based on + * st-texture-frame.h: Expandible texture actor + * + * Copyright 2007, 2008 OpenedHand Ltd + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_SUBTEXTURE_H__ +#define __ST_SUBTEXTURE_H__ + +#include + +G_BEGIN_DECLS + +#define ST_TYPE_SUBTEXTURE (st_subtexture_get_type ()) +#define ST_SUBTEXTURE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_SUBTEXTURE, StSubtexture)) +#define ST_SUBTEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_SUBTEXTURE, StSubtextureClass)) +#define ST_IS_SUBTEXTURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_SUBTEXTURE)) +#define ST_IS_SUBTEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_SUBTEXTURE)) +#define ST_SUBTEXTURE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_SUBTEXTURE, StSubtextureClass)) + +typedef struct _StSubtexture StSubtexture; +typedef struct _StSubtexturePrivate StSubtexturePrivate; +typedef struct _StSubtextureClass StSubtextureClass; + +/** + * StSubtexture: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +struct _StSubtexture +{ + /*< private >*/ + ClutterActor parent_instance; + + StSubtexturePrivate *priv; +}; + +struct _StSubtextureClass +{ + ClutterActorClass parent_class; + + /* padding for future expansion */ + void (*_st_box_1) (void); + void (*_st_box_2) (void); + void (*_st_box_3) (void); + void (*_st_box_4) (void); +}; + +GType st_subtexture_get_type (void) G_GNUC_CONST; + +ClutterActor * st_subtexture_new (ClutterTexture *texture, + gint top, + gint left, + gint width, + gint height); +void st_subtexture_set_parent_texture (StSubtexture *frame, + ClutterTexture *texture); +ClutterTexture *st_subtexture_get_parent_texture (StSubtexture *frame); +void st_subtexture_set_frame (StSubtexture *frame, + gint top, + gint left, + gint width, + gint height); +void st_subtexture_get_frame (StSubtexture *frame, + gint *top, + gint *left, + gint *width, + gint *height); + +G_END_DECLS + +#endif /* __ST_SUBTEXTURE_H__ */ diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c new file mode 100644 index 000000000..0cab32c32 --- /dev/null +++ b/src/st/st-texture-cache.c @@ -0,0 +1,450 @@ +/* + * st-widget.h: Base class for St actors + * + * Copyright 2007 OpenedHand + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +/** + * SECTION:st-texture-cache + * @short_description: A per-process store to cache textures + * + * #StTextureCache allows an application to re-use an previously loaded + * textures. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "st-texture-cache.h" +#include "st-marshal.h" +#include "st-private.h" +#include "st-subtexture.h" +G_DEFINE_TYPE (StTextureCache, st_texture_cache, G_TYPE_OBJECT) + +#define TEXTURE_CACHE_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_TEXTURE_CACHE, StTextureCachePrivate)) + +typedef struct _StTextureCachePrivate StTextureCachePrivate; + +struct _StTextureCachePrivate +{ + GHashTable *cache; +}; + +typedef struct FinalizedClosure +{ + gchar *path; + StTextureCache *cache; +} FinalizedClosure; + +enum +{ + PROP_0, +}; + +static StTextureCache* __cache_singleton = NULL; + +/* + * Convention: posX with a value of -1 indicates whole texture + */ +typedef struct StTextureCacheItem { + char filename[256]; + int width, height; + int posX, posY; + ClutterActor *ptr; +} StTextureCacheItem; + +static StTextureCacheItem * +st_texture_cache_item_new (void) +{ + return g_slice_new0 (StTextureCacheItem); +} + +static void +st_texture_cache_item_free (StTextureCacheItem *item) +{ + g_slice_free (StTextureCacheItem, item); +} + +static void +st_texture_cache_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +st_texture_cache_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +st_texture_cache_dispose (GObject *object) +{ + if (G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose) + G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object); +} + +static void +st_texture_cache_finalize (GObject *object) +{ + StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(object); + + if (priv->cache) + { + g_hash_table_unref (priv->cache); + priv->cache = NULL; + } + + G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object); +} + +static void +st_texture_cache_class_init (StTextureCacheClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (StTextureCachePrivate)); + + object_class->get_property = st_texture_cache_get_property; + object_class->set_property = st_texture_cache_set_property; + object_class->dispose = st_texture_cache_dispose; + object_class->finalize = st_texture_cache_finalize; + +} + +static void +st_texture_cache_init (StTextureCache *self) +{ + StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self); + + priv->cache = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + +} + +/** + * st_texture_cache_get_default: + * + * Returns the default texture cache. This is owned by St and should not be + * unreferenced or freed. + * + * Returns: a StTextureCache + */ +StTextureCache* +st_texture_cache_get_default (void) +{ + if (G_UNLIKELY (__cache_singleton == NULL)) + __cache_singleton = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL); + + return __cache_singleton; +} + +#if 0 +static void +on_texure_finalized (gpointer data, + GObject *where_the_object_was) +{ + FinalizedClosure *closure = (FinalizedClosure *) data; + StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(closure->cache); + + g_hash_table_remove (priv->cache, closure->path); + + g_free(closure->path); + g_free(closure); +} +#endif + +/** + * st_texture_cache_get_size: + * @self: A #StTextureCache + * + * Returns the number of items in the texture cache + * + * Returns: the current size of the cache + */ +gint +st_texture_cache_get_size (StTextureCache *self) +{ + StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self); + + return g_hash_table_size (priv->cache); +} + +static void +add_texture_to_cache (StTextureCache *self, + const gchar *path, + StTextureCacheItem *item) +{ + /* FinalizedClosure *closure; */ + StTextureCachePrivate *priv = TEXTURE_CACHE_PRIVATE(self); + + g_hash_table_insert (priv->cache, g_strdup (path), item); + +#if 0 + /* Make sure we can remove from hash */ + closure = g_new0 (FinalizedClosure, 1); + closure->path = g_strdup (path); + closure->cache = self; + + g_object_weak_ref (G_OBJECT (res), on_texure_finalized, closure); +#endif +} + +/* NOTE: you should unref the returned texture when not needed */ + +/** + * st_texture_cache_get_texture: + * @self: A #StTextureCache + * @path: A path to a image file + * + * Create a new ClutterTexture with the specified image. Adds the image to the + * cache if the image had not been previously loaded. Subsequent calls with + * the same image path will return a new ClutterTexture with the previously + * loaded image. + * + * Returns: a newly created ClutterTexture + */ +ClutterTexture* +st_texture_cache_get_texture (StTextureCache *self, + const gchar *path) +{ + ClutterActor *texture; + CoglHandle *handle; + StTextureCachePrivate *priv; + StTextureCacheItem *item; + + g_return_val_if_fail (ST_IS_TEXTURE_CACHE (self), NULL); + g_return_val_if_fail (path != NULL, NULL); + + + priv = TEXTURE_CACHE_PRIVATE (self); + + item = g_hash_table_lookup (priv->cache, path); + + if (item && item->posX != -1) + { + GError *err = NULL; + /* + * We have a cache hit, but it's for a partial texture. The only + * sane option is to read it from disk and just don't cache it + * at all. + */ + return CLUTTER_TEXTURE(clutter_texture_new_from_file(path, &err)); + } + if (!item) + { + GError *err = NULL; + + item = st_texture_cache_item_new (); + item->posX = -1; + item->posY = -1; + item->ptr = clutter_texture_new_from_file (path, &err); + clutter_texture_get_base_size (CLUTTER_TEXTURE (item->ptr), + &item->width, &item->height); + + if (!item->ptr) + { + if (err) + { + g_warning ("Error loading image: %s", err->message); + g_error_free (err); + } + + return NULL; + } + + add_texture_to_cache (self, path, item); + } + + texture = clutter_texture_new (); + handle = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (item->ptr)); + clutter_texture_set_cogl_texture ((ClutterTexture*) texture, handle); + + return (ClutterTexture*) texture; +} + + +/** + * st_texture_cache_get_actor: + * @self: A #StTextureCache + * @path: A path to a image file + * + * Create a new ClutterSubTexture with the specified image. Adds the image to the + * cache if the image had not been previously loaded. Subsequent calls with + * the same image path will return a new ClutterTexture with the previously + * loaded image. + * + * Use this function if all you need is an actor for drawing. + * + * Returns: a newly created ClutterTexture + */ +ClutterActor* +st_texture_cache_get_actor (StTextureCache *self, + const gchar *path) +{ + StTextureCachePrivate *priv; + StTextureCacheItem *item; + GError *err = NULL; + + g_return_val_if_fail (ST_IS_TEXTURE_CACHE (self), NULL); + g_return_val_if_fail (path != NULL, NULL); + + priv = TEXTURE_CACHE_PRIVATE (self); + + + item = g_hash_table_lookup (priv->cache, path); + + if (item) + { + int posX = item->posX; + int posY = item->posY; + if (posX == -1) + posX = 0; + if (posY == -1) + posY = 0; + return st_subtexture_new (CLUTTER_TEXTURE (item->ptr), posX, posY, + item->width, item->height); + } + + item = st_texture_cache_item_new (); + item->posX = -1; + item->posY = -1; + item->ptr = clutter_texture_new_from_file (path, &err); + clutter_texture_get_base_size (CLUTTER_TEXTURE (item->ptr), + &item->width, &item->height); + + if (!item->ptr) + { + if (err) + { + g_warning ("Error loading image: %s", err->message); + g_error_free (err); + } + + return NULL; + } + + add_texture_to_cache (self, path, item); + + return st_subtexture_new (CLUTTER_TEXTURE (item->ptr), 0, 0, item->width, + item->height); +} + +void +st_texture_cache_load_cache (StTextureCache *self, + const gchar *filename) +{ + FILE *file; + StTextureCacheItem *element, head; + int ret; + ClutterActor *actor; + GError *error = NULL; + StTextureCachePrivate *priv; + + g_return_if_fail (ST_IS_TEXTURE_CACHE (self)); + g_return_if_fail (filename != NULL); + + priv = TEXTURE_CACHE_PRIVATE (self); + + file = fopen(filename, "rm"); + if (!file) + return; + + ret = fread (&head, sizeof(StTextureCacheItem), 1, file); + if (ret < 0) + { + fclose (file); + return; + } + + /* check if we already if this texture in the cache */ + if (g_hash_table_lookup (priv->cache, head.filename)) + { + /* skip it, we're done */ + fclose (file); + return; + } + + actor = clutter_texture_new_from_file (head.filename, &error); + + if (error) + { + g_critical (G_STRLOC ": Error opening cache image file: %s", + error->message); + g_clear_error (&error); + fclose (file); + return; + } + + element = st_texture_cache_item_new (); + element->posX = -1; + element->posY = -1; + element->ptr = actor; + strncpy (element->filename, head.filename, 256); + clutter_texture_get_base_size (CLUTTER_TEXTURE (element->ptr), + &element->width, &element->height); + g_hash_table_insert (priv->cache, element->filename, element); + + while (!feof (file)) + { + element = st_texture_cache_item_new (); + ret = fread (element, sizeof (StTextureCacheItem), 1, file); + if (ret < 1) + { + /* end of file */ + st_texture_cache_item_free (element); + break; + } + + element->ptr = actor; + + if (g_hash_table_lookup (priv->cache, element->filename)) + { + /* file is already in the cache.... */ + st_texture_cache_item_free (element); + } else { + g_hash_table_insert (priv->cache, element->filename, element); + } + } +} diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h new file mode 100644 index 000000000..b3da32e68 --- /dev/null +++ b/src/st/st-texture-cache.h @@ -0,0 +1,95 @@ +/* + * st-texture-cache.h: Cached textures object + * + * Copyright 2007 OpenedHand + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef _ST_TEXTURE_CACHE +#define _ST_TEXTURE_CACHE + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_TEXTURE_CACHE st_texture_cache_get_type() + +#define ST_TEXTURE_CACHE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + ST_TYPE_TEXTURE_CACHE, StTextureCache)) + +#define ST_TEXTURE_CACHE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + ST_TYPE_TEXTURE_CACHE, StTextureCacheClass)) + +#define ST_IS_TEXTURE_CACHE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + ST_TYPE_TEXTURE_CACHE)) + +#define ST_IS_TEXTURE_CACHE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + ST_TYPE_TEXTURE_CACHE)) + +#define ST_TEXTURE_CACHE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + ST_TYPE_TEXTURE_CACHE, StTextureCacheClass)) + +/** + * StTextureCache: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +typedef struct { + /*< private >*/ + GObject parent; +} StTextureCache; + +typedef struct { + GObjectClass parent_class; + + void (* loaded) (StTextureCache *self, + const gchar *path, + ClutterTexture *texture); + + void (* error_loading) (StTextureCache *self, + GError *error); +} StTextureCacheClass; + +GType st_texture_cache_get_type (void); + +StTextureCache* st_texture_cache_get_default (void); + +ClutterTexture* st_texture_cache_get_texture (StTextureCache *self, + const gchar *path); +ClutterActor* st_texture_cache_get_actor (StTextureCache *self, + const gchar *path); + +gint st_texture_cache_get_size (StTextureCache *self); + +void st_texture_cache_load_cache (StTextureCache *self, + const char *filename); + +G_END_DECLS + +#endif /* _ST_TEXTURE_CACHE */ diff --git a/src/st/st-texture-frame.c b/src/st/st-texture-frame.c new file mode 100644 index 000000000..75d34308a --- /dev/null +++ b/src/st/st-texture-frame.c @@ -0,0 +1,620 @@ +/* + * st-texture-frame.h: Expandible texture actor + * + * Copyright 2007 OpenedHand + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +/** + * SECTION:st-texture-frame + * @short_description: Stretch a texture to fit the entire allocation + * + * #StTextureFrame + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "st-texture-frame.h" +#include "st-private.h" + +enum +{ + PROP_0, + + PROP_PARENT_TEXTURE, + + PROP_TOP, + PROP_RIGHT, + PROP_BOTTOM, + PROP_LEFT +}; + +G_DEFINE_TYPE (StTextureFrame, st_texture_frame, CLUTTER_TYPE_ACTOR); + +#define ST_TEXTURE_FRAME_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TEXTURE_FRAME, StTextureFramePrivate)) + +struct _StTextureFramePrivate +{ + ClutterTexture *parent_texture; + + gfloat top; + gfloat right; + gfloat bottom; + gfloat left; +}; + +static void +st_texture_frame_get_preferred_width (ClutterActor *self, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + StTextureFramePrivate *priv = ST_TEXTURE_FRAME (self)->priv; + + if (G_UNLIKELY (priv->parent_texture == NULL)) + { + if (min_width_p) + *min_width_p = 0; + + if (natural_width_p) + *natural_width_p = 0; + } + else + { + ClutterActorClass *klass; + + /* by directly querying the parent texture's class implementation + * we are going around any override mechanism the parent texture + * might have in place, and we ask directly for the original + * preferred width + */ + klass = CLUTTER_ACTOR_GET_CLASS (priv->parent_texture); + klass->get_preferred_width (CLUTTER_ACTOR (priv->parent_texture), + for_height, + min_width_p, + natural_width_p); + } +} + +static void +st_texture_frame_get_preferred_height (ClutterActor *self, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + StTextureFramePrivate *priv = ST_TEXTURE_FRAME (self)->priv; + + if (G_UNLIKELY (priv->parent_texture == NULL)) + { + if (min_height_p) + *min_height_p = 0; + + if (natural_height_p) + *natural_height_p = 0; + } + else + { + ClutterActorClass *klass; + + /* by directly querying the parent texture's class implementation + * we are going around any override mechanism the parent texture + * might have in place, and we ask directly for the original + * preferred height + */ + klass = CLUTTER_ACTOR_GET_CLASS (priv->parent_texture); + klass->get_preferred_height (CLUTTER_ACTOR (priv->parent_texture), + for_width, + min_height_p, + natural_height_p); + } +} + +static void +st_texture_frame_paint (ClutterActor *self) +{ + StTextureFramePrivate *priv = ST_TEXTURE_FRAME (self)->priv; + CoglHandle cogl_texture = COGL_INVALID_HANDLE; + CoglHandle cogl_material = COGL_INVALID_HANDLE; + ClutterActorBox box = { 0, }; + gfloat width, height; + gfloat tex_width, tex_height; + gfloat ex, ey; + gfloat tx1, ty1, tx2, ty2; + guint8 opacity; + + /* no need to paint stuff if we don't have a texture */ + if (G_UNLIKELY (priv->parent_texture == NULL)) + return; + + /* parent texture may have been hidden, so need to make sure it gets + * realized + */ + if (!CLUTTER_ACTOR_IS_REALIZED (priv->parent_texture)) + clutter_actor_realize (CLUTTER_ACTOR (priv->parent_texture)); + + cogl_texture = clutter_texture_get_cogl_texture (priv->parent_texture); + if (cogl_texture == COGL_INVALID_HANDLE) + return; + cogl_material = clutter_texture_get_cogl_material (priv->parent_texture); + if (cogl_material == COGL_INVALID_HANDLE) + return; + + tex_width = cogl_texture_get_width (cogl_texture); + tex_height = cogl_texture_get_height (cogl_texture); + + clutter_actor_get_allocation_box (self, &box); + width = box.x2 - box.x1; + height = box.y2 - box.y1; + + tx1 = priv->left / tex_width; + tx2 = (tex_width - priv->right) / tex_width; + ty1 = priv->top / tex_height; + ty2 = (tex_height - priv->bottom) / tex_height; + + ex = width - priv->right; + if (ex < 0) + ex = priv->right; /* FIXME ? */ + + ey = height - priv->bottom; + if (ey < 0) + ey = priv->bottom; /* FIXME ? */ + + opacity = clutter_actor_get_paint_opacity (self); + + /* Paint using the parent texture's material. It should already have + the cogl texture set as the first layer */ + /* NB: for correct blending we need set a preumultiplied color here: */ + cogl_material_set_color4ub (cogl_material, + opacity, opacity, opacity, opacity); + cogl_set_source (cogl_material); + + { + GLfloat rectangles[] = + { + /* top left corner */ + 0, 0, priv->left, priv->top, + 0.0, 0.0, + tx1, ty1, + + /* top middle */ + priv->left, 0, ex, priv->top, + tx1, 0.0, + tx2, ty1, + + /* top right */ + ex, 0, width, priv->top, + tx2, 0.0, + 1.0, ty1, + + /* mid left */ + 0, priv->top, priv->left, ey, + 0.0, ty1, + tx1, ty2, + + /* center */ + priv->left, priv->top, ex, ey, + tx1, ty1, + tx2, ty2, + + /* mid right */ + ex, priv->top, width, ey, + tx2, ty1, + 1.0, ty2, + + /* bottom left */ + 0, ey, priv->left, height, + 0.0, ty2, + tx1, 1.0, + + /* bottom center */ + priv->left, ey, ex, height, + tx1, ty2, + tx2, 1.0, + + /* bottom right */ + ex, ey, width, height, + tx2, ty2, + 1.0, 1.0 + }; + + cogl_rectangles_with_texture_coords (rectangles, 9); + } +} + +static inline void +st_texture_frame_set_frame_internal (StTextureFrame *frame, + gfloat top, + gfloat right, + gfloat bottom, + gfloat left) +{ + StTextureFramePrivate *priv = frame->priv; + GObject *gobject = G_OBJECT (frame); + gboolean changed = FALSE; + + g_object_freeze_notify (gobject); + + if (priv->top != top) + { + priv->top = top; + g_object_notify (gobject, "top"); + changed = TRUE; + } + + if (priv->right != right) + { + priv->right = right; + g_object_notify (gobject, "right"); + changed = TRUE; + } + + if (priv->bottom != bottom) + { + priv->bottom = bottom; + g_object_notify (gobject, "bottom"); + changed = TRUE; + } + + if (priv->left != left) + { + priv->left = left; + g_object_notify (gobject, "left"); + changed = TRUE; + } + + if (changed && CLUTTER_ACTOR_IS_VISIBLE (frame)) + clutter_actor_queue_redraw (CLUTTER_ACTOR (frame)); + + g_object_thaw_notify (gobject); +} + +static void +st_texture_frame_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StTextureFrame *frame = ST_TEXTURE_FRAME (gobject); + StTextureFramePrivate *priv = frame->priv; + + switch (prop_id) + { + case PROP_PARENT_TEXTURE: + st_texture_frame_set_parent_texture (frame, + g_value_get_object (value)); + break; + + case PROP_TOP: + st_texture_frame_set_frame_internal (frame, + g_value_get_float (value), + priv->right, + priv->bottom, + priv->left); + break; + + case PROP_RIGHT: + st_texture_frame_set_frame_internal (frame, + priv->top, + g_value_get_float (value), + priv->bottom, + priv->left); + break; + + case PROP_BOTTOM: + st_texture_frame_set_frame_internal (frame, + priv->top, + priv->right, + g_value_get_float (value), + priv->left); + break; + + case PROP_LEFT: + st_texture_frame_set_frame_internal (frame, + priv->top, + priv->right, + priv->bottom, + g_value_get_float (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_texture_frame_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StTextureFramePrivate *priv = ST_TEXTURE_FRAME (gobject)->priv; + + switch (prop_id) + { + case PROP_PARENT_TEXTURE: + g_value_set_object (value, priv->parent_texture); + break; + + case PROP_LEFT: + g_value_set_float (value, priv->left); + break; + + case PROP_TOP: + g_value_set_float (value, priv->top); + break; + + case PROP_RIGHT: + g_value_set_float (value, priv->right); + break; + + case PROP_BOTTOM: + g_value_set_float (value, priv->bottom); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_texture_frame_dispose (GObject *gobject) +{ + StTextureFramePrivate *priv = ST_TEXTURE_FRAME (gobject)->priv; + + if (priv->parent_texture) + { + g_object_unref (priv->parent_texture); + priv->parent_texture = NULL; + } + + G_OBJECT_CLASS (st_texture_frame_parent_class)->dispose (gobject); +} + +static void +st_texture_frame_class_init (StTextureFrameClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (gobject_class, sizeof (StTextureFramePrivate)); + + actor_class->get_preferred_width = + st_texture_frame_get_preferred_width; + actor_class->get_preferred_height = + st_texture_frame_get_preferred_height; + actor_class->paint = st_texture_frame_paint; + + gobject_class->set_property = st_texture_frame_set_property; + gobject_class->get_property = st_texture_frame_get_property; + gobject_class->dispose = st_texture_frame_dispose; + + pspec = g_param_spec_object ("parent-texture", + "Parent Texture", + "The parent ClutterTexture", + CLUTTER_TYPE_TEXTURE, + ST_PARAM_READWRITE | + G_PARAM_CONSTRUCT); + g_object_class_install_property (gobject_class, PROP_PARENT_TEXTURE, pspec); + + pspec = g_param_spec_float ("left", + "Left", + "Left offset", + 0, G_MAXFLOAT, + 0, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_LEFT, pspec); + + pspec = g_param_spec_float ("top", + "Top", + "Top offset", + 0, G_MAXFLOAT, + 0, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_TOP, pspec); + + pspec = g_param_spec_float ("bottom", + "Bottom", + "Bottom offset", + 0, G_MAXFLOAT, + 0, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_BOTTOM, pspec); + + pspec = g_param_spec_float ("right", + "Right", + "Right offset", + 0, G_MAXFLOAT, + 0, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_RIGHT, pspec); +} + +static void +st_texture_frame_init (StTextureFrame *self) +{ + StTextureFramePrivate *priv; + + self->priv = priv = ST_TEXTURE_FRAME_GET_PRIVATE (self); +} + +/** + * st_texture_frame_new: + * @texture: a #ClutterTexture or %NULL + * @left: left margin preserving its content + * @top: top margin preserving its content + * @right: right margin preserving its content + * @bottom: bottom margin preserving its content + * + * A #StTextureFrame is a specialized texture that efficiently clones + * an area of the given @texture while keeping preserving portions of the + * same texture. + * + * A #StTextureFrame can be used to make a rectangular texture fit a + * given size without stretching its borders. + * + * Return value: the newly created #StTextureFrame + */ +ClutterActor* +st_texture_frame_new (ClutterTexture *texture, + gfloat top, + gfloat right, + gfloat bottom, + gfloat left) +{ + g_return_val_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture), NULL); + + return g_object_new (ST_TYPE_TEXTURE_FRAME, + "parent-texture", texture, + "top", top, + "right", right, + "bottom", bottom, + "left", left, + NULL); +} + +/** + * st_texture_frame_get_parent_texture: + * @frame: A #StTextureFrame + * + * Return the texture used by the #StTextureFrame + * + * Returns: a #ClutterTexture owned by the #StTextureFrame + */ +ClutterTexture * +st_texture_frame_get_parent_texture (StTextureFrame *frame) +{ + g_return_val_if_fail (ST_IS_TEXTURE_FRAME (frame), NULL); + + return frame->priv->parent_texture; +} + +/** + * st_texture_frame_set_parent_texture: + * @frame: A #StTextureFrame + * @texture: A #ClutterTexture + * + * Set the #ClutterTexture used by this #StTextureFrame + * + */ +void +st_texture_frame_set_parent_texture (StTextureFrame *frame, + ClutterTexture *texture) +{ + StTextureFramePrivate *priv; + gboolean was_visible; + + g_return_if_fail (ST_IS_TEXTURE_FRAME (frame)); + g_return_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture)); + + priv = frame->priv; + + was_visible = CLUTTER_ACTOR_IS_VISIBLE (frame); + + if (priv->parent_texture == texture) + return; + + if (priv->parent_texture) + { + g_object_unref (priv->parent_texture); + priv->parent_texture = NULL; + + if (was_visible) + clutter_actor_hide (CLUTTER_ACTOR (frame)); + } + + if (texture) + { + priv->parent_texture = g_object_ref_sink (texture); + + if (was_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->parent_texture)) + clutter_actor_show (CLUTTER_ACTOR (frame)); + } + + clutter_actor_queue_relayout (CLUTTER_ACTOR (frame)); + + g_object_notify (G_OBJECT (frame), "parent-texture"); +} + +/** + * st_texture_frame_set_frame: + * @frame: A #StTextureFrame + * @top: width of the top slice + * @right: width of the right slice + * @bottom: width of the bottom slice + * @left: width of the left slice + * + * Set the slice lines of the specified frame. The slices are calculated as + * widths from the edge of the frame. + * + */ +void +st_texture_frame_set_frame (StTextureFrame *frame, + gfloat top, + gfloat right, + gfloat bottom, + gfloat left) +{ + g_return_if_fail (ST_IS_TEXTURE_FRAME (frame)); + + st_texture_frame_set_frame_internal (frame, top, right, bottom, left); +} + +/** + * st_texture_frame_get_frame: + * @frame: A #StTextureFrame + * @top: width of the top slice + * @right: width of the right slice + * @bottom: width of the bottom slice + * @left: width of the left slice + * + * Retrieve the current slice lines from the specified frame. + * + */ +void +st_texture_frame_get_frame (StTextureFrame *frame, + gfloat *top, + gfloat *right, + gfloat *bottom, + gfloat *left) +{ + StTextureFramePrivate *priv; + + g_return_if_fail (ST_IS_TEXTURE_FRAME (frame)); + + priv = frame->priv; + + if (top) + *top = priv->top; + + if (right) + *right = priv->right; + + if (bottom) + *bottom = priv->bottom; + + if (left) + *left = priv->left; +} diff --git a/src/st/st-texture-frame.h b/src/st/st-texture-frame.h new file mode 100644 index 000000000..240a1c0fb --- /dev/null +++ b/src/st/st-texture-frame.h @@ -0,0 +1,93 @@ +/* + * st-texture-frame.h: Expandible texture actor + * + * Copyright 2007, 2008 OpenedHand Ltd + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_TEXTURE_FRAME_H__ +#define __ST_TEXTURE_FRAME_H__ + +#include + +G_BEGIN_DECLS + +#define ST_TYPE_TEXTURE_FRAME (st_texture_frame_get_type ()) +#define ST_TEXTURE_FRAME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_TEXTURE_FRAME, StTextureFrame)) +#define ST_TEXTURE_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_TEXTURE_FRAME, StTextureFrameClass)) +#define ST_IS_TEXTURE_FRAME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_TEXTURE_FRAME)) +#define ST_IS_TEXTURE_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_TEXTURE_FRAME)) +#define ST_TEXTURE_FRAME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_TEXTURE_FRAME, StTextureFrameClass)) + +typedef struct _StTextureFrame StTextureFrame; +typedef struct _StTextureFramePrivate StTextureFramePrivate; +typedef struct _StTextureFrameClass StTextureFrameClass; + +/** + * StTextureFrame: + * + * The contents of this structure are private and should only be accessed + * through the public API. + */ +struct _StTextureFrame +{ + /*< private >*/ + ClutterActor parent_instance; + + StTextureFramePrivate *priv; +}; + +struct _StTextureFrameClass +{ + ClutterActorClass parent_class; + + /* padding for future expansion */ + void (*_clutter_box_1) (void); + void (*_clutter_box_2) (void); + void (*_clutter_box_3) (void); + void (*_clutter_box_4) (void); +}; + +GType st_texture_frame_get_type (void) G_GNUC_CONST; + +ClutterActor * st_texture_frame_new (ClutterTexture *texture, + gfloat top, + gfloat right, + gfloat bottom, + gfloat left); +void st_texture_frame_set_parent_texture (StTextureFrame *frame, + ClutterTexture *texture); +ClutterTexture *st_texture_frame_get_parent_texture (StTextureFrame *frame); +void st_texture_frame_set_frame (StTextureFrame *frame, + gfloat top, + gfloat right, + gfloat bottom, + gfloat left); +void st_texture_frame_get_frame (StTextureFrame *frame, + gfloat *top, + gfloat *right, + gfloat *bottom, + gfloat *left); + +G_END_DECLS + +#endif /* __ST_TEXTURE_FRAME_H__ */ diff --git a/src/st/st-tooltip.c b/src/st/st-tooltip.c new file mode 100644 index 000000000..5070945c6 --- /dev/null +++ b/src/st/st-tooltip.c @@ -0,0 +1,710 @@ +/* + * st-tooltip.c: Plain tooltip actor + * + * Copyright 2008, 2009 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Thomas Wood + * + */ + +/** + * SECTION:st-tooltip + * @short_description: A tooltip widget + * + * #StTooltip implements a single tooltip. It should not normally be created + * by the application but by the widget implementing tooltip capabilities, for + * example, #st_button_set_tooltip(). + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include + +#include "st-tooltip.h" + +#include "st-widget.h" +#include "st-stylable.h" +#include "st-private.h" + +enum +{ + PROP_0, + + PROP_LABEL, + PROP_TIP_AREA +}; + +#define ST_TOOLTIP_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TOOLTIP, StTooltipPrivate)) + +struct _StTooltipPrivate +{ + ClutterActor *label; + + gfloat arrow_offset; + gboolean actor_below; + + ClutterGeometry *tip_area; +}; + +G_DEFINE_TYPE (StTooltip, st_tooltip, ST_TYPE_WIDGET); + +static void +st_tooltip_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StTooltip *tooltip = ST_TOOLTIP (gobject); + + switch (prop_id) + { + case PROP_LABEL: + st_tooltip_set_label (tooltip, g_value_get_string (value)); + break; + + case PROP_TIP_AREA: + st_tooltip_set_tip_area (tooltip, g_value_get_boxed (value)); + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_tooltip_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StTooltipPrivate *priv = ST_TOOLTIP (gobject)->priv; + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->label))); + break; + + case PROP_TIP_AREA: + g_value_set_boxed (value, priv->tip_area); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_tooltip_style_changed (StWidget *self) +{ + ClutterColor *color = NULL; + StTooltipPrivate *priv; + gchar *font_name; + gchar *font_string; + gint font_size; + + priv = ST_TOOLTIP (self)->priv; + + st_stylable_get (ST_STYLABLE (self), + "color", &color, + "font-family", &font_name, + "font-size", &font_size, + NULL); + + if (color) + { + clutter_text_set_color (CLUTTER_TEXT (priv->label), color); + clutter_color_free (color); + } + + if (font_name || font_size) + { + if (font_name && font_size) + { + font_string = g_strdup_printf ("%s %dpx", font_name, font_size); + g_free (font_name); + } + else + if (font_size) + font_string = g_strdup_printf ("%dpx", font_size); + else + font_string = font_name; + + clutter_text_set_font_name (CLUTTER_TEXT (priv->label), font_string); + + g_free (font_string); + } + +} + +static void +st_tooltip_get_preferred_width (ClutterActor *self, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; + gfloat min_label_w, natural_label_w; + gfloat label_height, arrow_height; + ClutterActor *arrow_image; + StPadding padding; + + st_widget_get_padding (ST_WIDGET (self), &padding); + + arrow_image = st_widget_get_background_image (ST_WIDGET (self)); + if (arrow_image) + { + clutter_actor_get_preferred_height (arrow_image, + -1, + NULL, + &arrow_height); + } + else + { + arrow_height = 0; + } + + if (for_height > -1) + { + label_height = for_height - arrow_height - padding.top - padding.bottom; + } + else + { + label_height = -1; + } + + if (priv->label) + { + clutter_actor_get_preferred_width (priv->label, + label_height, + &min_label_w, + &natural_label_w); + } + else + { + min_label_w = 0; + natural_label_w = 0; + } + + + if (min_width_p) + { + *min_width_p = padding.left + padding.right + min_label_w; + } + + if (natural_width_p) + { + *natural_width_p = padding.left + padding.right + natural_label_w; + } +} + +static void +st_tooltip_get_preferred_height (ClutterActor *self, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; + gfloat arrow_height; + gfloat min_label_h, natural_label_h; + gfloat label_width; + ClutterActor *arrow_image; + StPadding padding; + + arrow_image = st_widget_get_background_image (ST_WIDGET (self)); + + if (arrow_image && !priv->actor_below) + { + clutter_actor_get_preferred_height (arrow_image, + -1, + NULL, + &arrow_height); + } + else + { + arrow_height = 0; + } + st_widget_get_padding (ST_WIDGET (self), &padding); + + if (for_width > -1) + { + label_width = for_width - padding.left - padding.right; + } + else + { + label_width = -1; + } + + if (priv->label) + { + clutter_actor_get_preferred_height (priv->label, + label_width, + &min_label_h, + &natural_label_h); + } + else + { + min_label_h = 0; + natural_label_h = 0; + } + + if (min_height_p) + { + *min_height_p = padding.top + padding.bottom + + arrow_height + min_label_h; + } + + if (natural_height_p) + { + *natural_height_p = padding.top + padding.bottom + + arrow_height + natural_label_h; + } +} + +static void +st_tooltip_allocate (ClutterActor *self, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; + ClutterActorBox child_box, arrow_box; + gfloat arrow_height, arrow_width; + ClutterActor *border_image, *arrow_image; + StPadding padding; + + CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->allocate (self, + box, + flags); + + st_widget_get_padding (ST_WIDGET (self), &padding); + + arrow_image = st_widget_get_background_image (ST_WIDGET (self)); + + if (arrow_image && !priv->actor_below) + { + clutter_actor_get_preferred_height (arrow_image, -1, NULL, &arrow_height); + clutter_actor_get_preferred_width (arrow_image, -1, NULL, &arrow_width); + + arrow_box.x1 = (float)(priv->arrow_offset) - (int)(arrow_width / 2); + arrow_box.y1 = 0; + arrow_box.x2 = arrow_box.x1 + arrow_width; + arrow_box.y2 = arrow_box.y1 + arrow_height; + + clutter_actor_allocate (arrow_image, &arrow_box, flags); + } + else + { + arrow_height = 0; + arrow_width = 0; + } + + child_box.x1 = child_box.y1 = 0; + child_box.x2 = (box->x2 - box->x1); + child_box.y2 = (box->y2 - box->y1); + + /* remove the space that is used by the arrow */ + child_box.y1 += arrow_height; + + border_image = st_widget_get_border_image (ST_WIDGET (self)); + if (border_image) + clutter_actor_allocate (border_image, &child_box, flags); + + if (priv->label) + { + /* now remove the padding */ + child_box.y1 += padding.top; + child_box.x1 += padding.left; + child_box.x2 -= padding.right; + child_box.y2 -= padding.bottom; + + clutter_actor_allocate (priv->label, &child_box, flags); + } +} + + +static void +st_tooltip_paint (ClutterActor *self) +{ + ClutterActor *border_image, *arrow_image; + StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; + + border_image = st_widget_get_border_image (ST_WIDGET (self)); + if (border_image) + clutter_actor_paint (border_image); + + arrow_image = st_widget_get_background_image (ST_WIDGET (self)); + if (arrow_image && !priv->actor_below) + clutter_actor_paint (arrow_image); + + clutter_actor_paint (priv->label); +} + +static void +st_tooltip_map (ClutterActor *self) +{ + StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; + ClutterActor *border_image, *arrow_image; + + CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->map (self); + + border_image = st_widget_get_border_image (ST_WIDGET (self)); + if (border_image) + clutter_actor_map (border_image); + + arrow_image = st_widget_get_background_image (ST_WIDGET (self)); + if (arrow_image) + clutter_actor_map (arrow_image); + + clutter_actor_map (priv->label); +} + +static void +st_tooltip_unmap (ClutterActor *self) +{ + StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; + ClutterActor *border_image, *arrow_image; + + CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->unmap (self); + + border_image = st_widget_get_border_image (ST_WIDGET (self)); + if (border_image) + clutter_actor_unmap (border_image); + + arrow_image = st_widget_get_background_image (ST_WIDGET (self)); + if (arrow_image) + clutter_actor_unmap (arrow_image); + + clutter_actor_unmap (priv->label); +} + +static void +st_tooltip_class_init (StTooltipClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (StTooltipPrivate)); + + gobject_class->set_property = st_tooltip_set_property; + gobject_class->get_property = st_tooltip_get_property; + + actor_class->get_preferred_width = st_tooltip_get_preferred_width; + actor_class->get_preferred_height = st_tooltip_get_preferred_height; + actor_class->allocate = st_tooltip_allocate; + actor_class->paint = st_tooltip_paint; + actor_class->map = st_tooltip_map; + actor_class->unmap = st_tooltip_unmap; + + pspec = g_param_spec_string ("label", + "Label", + "Label of the tooltip", + NULL, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_LABEL, pspec); + + pspec = g_param_spec_boxed ("tip-area", + "Tip Area", + "Area on the stage the tooltip applies to", + CLUTTER_TYPE_GEOMETRY, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_TIP_AREA, pspec); +} + +static void +st_tooltip_init (StTooltip *tooltip) +{ + tooltip->priv = ST_TOOLTIP_GET_PRIVATE (tooltip); + + tooltip->priv->label = g_object_new (CLUTTER_TYPE_TEXT, + "line-alignment", PANGO_ALIGN_CENTER, + "ellipsize", PANGO_ELLIPSIZE_END, + "use-markup", TRUE, + NULL); + + tooltip->priv->tip_area = NULL; + + clutter_actor_set_parent (CLUTTER_ACTOR (tooltip->priv->label), + CLUTTER_ACTOR (tooltip)); + + g_object_set (tooltip, "show-on-set-parent", FALSE, NULL); + + clutter_actor_set_reactive (CLUTTER_ACTOR (tooltip), FALSE); + + g_signal_connect (tooltip, "style-changed", + G_CALLBACK (st_tooltip_style_changed), NULL); +} + +static void +st_tooltip_update_position (StTooltip *tooltip) +{ + StTooltipPrivate *priv = tooltip->priv; + ClutterGeometry *tip_area = tooltip->priv->tip_area; + gfloat tooltip_w, tooltip_h, tooltip_x, tooltip_y; + gfloat stage_w, stage_h; + ClutterActor *stage; + + /* ensure the tooltip with is not fixed size */ + clutter_actor_set_size ((ClutterActor*) tooltip, -1, -1); + + /* if no area set, just position ourselves top left */ + if (!priv->tip_area) + { + clutter_actor_set_position ((ClutterActor*) tooltip, 0, 0); + return; + } + + /* we need to have a style in case there are padding values to take into + * account when calculating width/height */ + st_widget_ensure_style ((StWidget *) tooltip); + + /* find out the tooltip's size */ + clutter_actor_get_size ((ClutterActor*) tooltip, &tooltip_w, &tooltip_h); + + /* attempt to place the tooltip */ + tooltip_x = (int)(tip_area->x + (tip_area->width / 2) - (tooltip_w / 2)); + tooltip_y = (int)(tip_area->y + tip_area->height); + + stage = clutter_actor_get_stage ((ClutterActor *) tooltip); + if (!stage) + { + return; + } + clutter_actor_get_size (stage, &stage_w, &stage_h); + + /* make sure the tooltip is not off screen vertically */ + if (tooltip_w > stage_w) + { + tooltip_x = 0; + clutter_actor_set_width ((ClutterActor*) tooltip, stage_w); + } + else if (tooltip_x < 0) + { + tooltip_x = 0; + } + else if (tooltip_x + tooltip_w > stage_w) + { + tooltip_x = (int)(stage_w) - tooltip_w; + } + + /* make sure the tooltip is not off screen horizontally */ + if (tooltip_y + tooltip_h > stage_h) + { + priv->actor_below = TRUE; + + /* re-query size as may have changed */ + clutter_actor_get_preferred_height ((ClutterActor*) tooltip, + -1, NULL, &tooltip_h); + tooltip_y = tip_area->y - tooltip_h; + } + else + { + priv->actor_below = FALSE; + } + + /* calculate the arrow offset */ + priv->arrow_offset = tip_area->x + tip_area->width / 2 - tooltip_x; + + clutter_actor_set_position ((ClutterActor*) tooltip, tooltip_x, tooltip_y); +} + +/** + * st_tooltip_get_label: + * @tooltip: a #StTooltip + * + * Get the text displayed on the tooltip + * + * Returns: the text for the tooltip. This must not be freed by the application + */ +G_CONST_RETURN gchar * +st_tooltip_get_label (StTooltip *tooltip) +{ + g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL); + + return clutter_text_get_text (CLUTTER_TEXT (tooltip->priv->label)); +} + +/** + * st_tooltip_set_label: + * @tooltip: a #StTooltip + * @text: text to set the label to + * + * Sets the text displayed on the tooltip + */ +void +st_tooltip_set_label (StTooltip *tooltip, + const gchar *text) +{ + StTooltipPrivate *priv; + + g_return_if_fail (ST_IS_TOOLTIP (tooltip)); + + priv = tooltip->priv; + + clutter_text_set_text (CLUTTER_TEXT (priv->label), text); + + g_object_notify (G_OBJECT (tooltip), "label"); +} + +/** + * st_tooltip_show: + * @tooltip: a #StTooltip + * + * Show the tooltip relative to the associated widget. + */ +void +st_tooltip_show (StTooltip *tooltip) +{ + StTooltipPrivate *priv; + ClutterActor *parent; + ClutterActor *stage; + ClutterActor *self = CLUTTER_ACTOR (tooltip); + ClutterAnimation *animation; + + /* make sure we're not currently already animating (e.g. hiding) */ + animation = clutter_actor_get_animation (CLUTTER_ACTOR (tooltip)); + if (animation) + clutter_animation_completed (animation); + + priv = tooltip->priv; + parent = clutter_actor_get_parent (self); + stage = clutter_actor_get_stage (self); + + if (!stage) + { + g_warning ("StTooltip is not on any stage."); + return; + } + + /* make sure we're parented on the stage */ + if (G_UNLIKELY (parent != stage)) + { + g_object_ref (self); + clutter_actor_unparent (self); + clutter_actor_set_parent (self, stage); + g_object_unref (self); + parent = stage; + } + + /* raise the tooltip to the top */ + clutter_container_raise_child (CLUTTER_CONTAINER (stage), + CLUTTER_ACTOR (tooltip), + NULL); + + st_tooltip_update_position (tooltip); + + /* finally show the tooltip... */ + CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->show (self); + + /* and give it some bounce! */ + g_object_set (G_OBJECT (self), + "scale-center-x", priv->arrow_offset, + "scale-center-y", (priv->actor_below) ? clutter_actor_get_height (self) : 0, + NULL); + clutter_actor_set_scale (self, 0.0, 0.0); + clutter_actor_animate (self, CLUTTER_EASE_OUT_ELASTIC, + 500, + "scale-x", 1.0, + "scale-y", 1.0, + NULL); +} + +static void +st_tooltip_hide_complete (ClutterAnimation *animation, + ClutterActor *actor) +{ + CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->hide (actor); + g_signal_handlers_disconnect_by_func (actor, + st_tooltip_hide_complete, + actor); +} + +/** + * st_tooltip_hide: + * @tooltip: a #StTooltip + * + * Hide the tooltip + */ +void +st_tooltip_hide (StTooltip *tooltip) +{ + ClutterAnimation *animation; + + g_return_if_fail (ST_TOOLTIP (tooltip)); + + /* make sure we're not currently already animating (e.g. hiding) */ + animation = clutter_actor_get_animation (CLUTTER_ACTOR (tooltip)); + if (animation) + clutter_animation_completed (animation); + + g_object_set (G_OBJECT (tooltip), + "scale-center-x", tooltip->priv->arrow_offset, + NULL); + animation = + clutter_actor_animate (CLUTTER_ACTOR (tooltip), CLUTTER_EASE_IN_SINE, + 150, + "scale-x", 0.0, + "scale-y", 0.0, + NULL); + g_signal_connect (animation, "completed", + G_CALLBACK (st_tooltip_hide_complete), tooltip); +} + +/** + * st_tooltip_set_tip_area: + * @tooltip: A #StTooltip + * @area: A #ClutterGeometry + * + * Set the area on the stage that the tooltip applies to. + */ +void +st_tooltip_set_tip_area (StTooltip *tooltip, + const ClutterGeometry *area) +{ + g_return_if_fail (ST_IS_TOOLTIP (tooltip)); + + if (tooltip->priv->tip_area) + g_boxed_free (CLUTTER_TYPE_GEOMETRY, tooltip->priv->tip_area); + tooltip->priv->tip_area = g_boxed_copy (CLUTTER_TYPE_GEOMETRY, area); + + st_tooltip_update_position (tooltip); +} + +/** + * st_tooltip_get_tip_area: + * @tooltip: A #StTooltip + * + * Retrieve the area on the stage that the tooltip currently applies to + * + * Returns: the #ClutterGeometry, owned by the tooltip which must not be freed + * by the application. + */ +G_CONST_RETURN ClutterGeometry* +st_tooltip_get_tip_area (StTooltip *tooltip) +{ + g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL); + + return tooltip->priv->tip_area; +} diff --git a/src/st/st-tooltip.h b/src/st/st-tooltip.h new file mode 100644 index 000000000..b2cef3e8c --- /dev/null +++ b/src/st/st-tooltip.h @@ -0,0 +1,80 @@ +/* + * st-tooltip.h: Plain tooltip actor + * + * Copyright 2008, 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Written by: Thomas Wood + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_TOOLTIP_H__ +#define __ST_TOOLTIP_H__ + +G_BEGIN_DECLS + +#include + +#define ST_TYPE_TOOLTIP (st_tooltip_get_type ()) +#define ST_TOOLTIP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_TOOLTIP, StTooltip)) +#define ST_IS_TOOLTIP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_TOOLTIP)) +#define ST_TOOLTIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_TOOLTIP, StTooltipClass)) +#define ST_IS_TOOLTIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_TOOLTIP)) +#define ST_TOOLTIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_TOOLTIP, StTooltipClass)) + +typedef struct _StTooltip StTooltip; +typedef struct _StTooltipPrivate StTooltipPrivate; +typedef struct _StTooltipClass StTooltipClass; + +/** + * StTooltip: + * + * The contents of this structure is private and should only be accessed using + * the provided API. + */ +struct _StTooltip +{ + /*< private >*/ + StBin parent_instance; + + StTooltipPrivate *priv; +}; + +struct _StTooltipClass +{ + StBinClass parent_class; +}; + +GType st_tooltip_get_type (void) G_GNUC_CONST; + +G_CONST_RETURN gchar *st_tooltip_get_label (StTooltip *tooltip); +void st_tooltip_set_label (StTooltip *tooltip, + const gchar *text); +void st_tooltip_show (StTooltip *tooltip); +void st_tooltip_hide (StTooltip *tooltip); + +void st_tooltip_set_tip_area (StTooltip *tooltip, + const ClutterGeometry *area); +G_CONST_RETURN ClutterGeometry* st_tooltip_get_tip_area (StTooltip *tooltip); + +G_END_DECLS + +#endif /* __ST_TOOLTIP_H__ */ diff --git a/src/st/st-types.h b/src/st/st-types.h new file mode 100644 index 000000000..204994585 --- /dev/null +++ b/src/st/st-types.h @@ -0,0 +1,73 @@ +/* + * Copyright 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/** + * SECTION:st-types + * @short_description: type definitions used throughout St + * + * Common types for StWidgets. + */ + + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_TYPES_H__ +#define __ST_TYPES_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_BORDER_IMAGE (st_border_image_get_type ()) +#define ST_TYPE_PADDING (st_padding_get_type ()) + +typedef struct _StPadding StPadding; + +GType st_border_image_get_type (void) G_GNUC_CONST; + +/** + * StPadding: + * @top: padding from the top + * @right: padding from the right + * @bottom: padding from the bottom + * @left: padding from the left + * + * The padding from the internal border of the parent container. + */ +struct _StPadding +{ + gfloat top; + gfloat right; + gfloat bottom; + gfloat left; +}; + +GType st_padding_get_type (void) G_GNUC_CONST; + +typedef enum { + ST_ALIGN_START, + ST_ALIGN_MIDDLE, + ST_ALIGN_END +} StAlign; + +G_END_DECLS + +#endif /* __ST_TYPES_H__ */ diff --git a/src/st/st-widget.c b/src/st/st-widget.c new file mode 100644 index 000000000..bcc4a2cf2 --- /dev/null +++ b/src/st/st-widget.c @@ -0,0 +1,1357 @@ +/* + * st-widget.c: Base class for St actors + * + * Copyright 2007 OpenedHand + * Copyright 2008, 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + * Thomas Wood + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include "st-widget.h" + +#include "st-marshal.h" +#include "st-private.h" +#include "st-stylable.h" +#include "st-texture-cache.h" +#include "st-texture-frame.h" +#include "st-tooltip.h" + +typedef ccss_border_image_t StBorderImage; + +/* + * Forward declaration for sake of StWidgetChild + */ +struct _StWidgetPrivate +{ + StPadding border; + StPadding padding; + + StStyle *style; + gchar *pseudo_class; + gchar *style_class; + + ClutterActor *border_image; + ClutterActor *background_image; + ClutterColor *bg_color; + + gboolean is_stylable : 1; + gboolean has_tooltip : 1; + gboolean is_style_dirty : 1; + + StTooltip *tooltip; +}; + +/** + * SECTION:st-widget + * @short_description: Base class for stylable actors + * + * #StWidget is a simple abstract class on top of #ClutterActor. It + * provides basic themeing properties. + * + * Actors in the St library should subclass #StWidget if they plan + * to obey to a certain #StStyle. + */ + +enum +{ + PROP_0, + + PROP_STYLE, + PROP_PSEUDO_CLASS, + PROP_STYLE_CLASS, + + PROP_STYLABLE, + + PROP_HAS_TOOLTIP, + PROP_TOOLTIP_TEXT +}; + +static void st_stylable_iface_init (StStylableIface *iface); + + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (StWidget, st_widget, CLUTTER_TYPE_ACTOR, + G_IMPLEMENT_INTERFACE (ST_TYPE_STYLABLE, + st_stylable_iface_init)); + +#define ST_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_WIDGET, StWidgetPrivate)) + +static void +st_widget_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StWidget *actor = ST_WIDGET (gobject); + + switch (prop_id) + { + case PROP_STYLE: + st_stylable_set_style (ST_STYLABLE (actor), + g_value_get_object (value)); + break; + + case PROP_PSEUDO_CLASS: + st_widget_set_style_pseudo_class (actor, g_value_get_string (value)); + break; + + case PROP_STYLE_CLASS: + st_widget_set_style_class_name (actor, g_value_get_string (value)); + break; + + case PROP_STYLABLE: + if (actor->priv->is_stylable != g_value_get_boolean (value)) + { + actor->priv->is_stylable = g_value_get_boolean (value); + clutter_actor_queue_relayout ((ClutterActor *) gobject); + } + break; + + case PROP_HAS_TOOLTIP: + st_widget_set_has_tooltip (actor, g_value_get_boolean (value)); + break; + + case PROP_TOOLTIP_TEXT: + st_widget_set_tooltip_text (actor, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_widget_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StWidget *actor = ST_WIDGET (gobject); + StWidgetPrivate *priv = actor->priv; + + switch (prop_id) + { + case PROP_STYLE: + g_value_set_object (value, priv->style); + break; + + case PROP_PSEUDO_CLASS: + g_value_set_string (value, priv->pseudo_class); + break; + + case PROP_STYLE_CLASS: + g_value_set_string (value, priv->style_class); + break; + + case PROP_STYLABLE: + g_value_set_boolean (value, priv->is_stylable); + break; + + case PROP_HAS_TOOLTIP: + g_value_set_boolean (value, priv->has_tooltip); + break; + + case PROP_TOOLTIP_TEXT: + g_value_set_string (value, st_widget_get_tooltip_text (actor)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_widget_dispose (GObject *gobject) +{ + StWidget *actor = ST_WIDGET (gobject); + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + + if (priv->style) + { + g_object_unref (priv->style); + priv->style = NULL; + } + + if (priv->border_image) + { + clutter_actor_unparent (priv->border_image); + priv->border_image = NULL; + } + + if (priv->bg_color) + { + clutter_color_free (priv->bg_color); + priv->bg_color = NULL; + } + + if (priv->tooltip) + { + ClutterContainer *parent; + ClutterActor *tooltip = CLUTTER_ACTOR (priv->tooltip); + + /* this is just a little bit awkward because the tooltip is parented + * on the stage, but we still want to "own" it */ + parent = CLUTTER_CONTAINER (clutter_actor_get_parent (tooltip)); + + if (parent) + clutter_container_remove_actor (parent, tooltip); + + priv->tooltip = NULL; + } + + G_OBJECT_CLASS (st_widget_parent_class)->dispose (gobject); +} + +static void +st_widget_finalize (GObject *gobject) +{ + StWidgetPrivate *priv = ST_WIDGET (gobject)->priv; + + g_free (priv->style_class); + g_free (priv->pseudo_class); + + G_OBJECT_CLASS (st_widget_parent_class)->finalize (gobject); +} + +static void +st_widget_allocate (ClutterActor *actor, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + ClutterActorClass *klass; + ClutterGeometry area; + ClutterVertex in_v, out_v; + + klass = CLUTTER_ACTOR_CLASS (st_widget_parent_class); + klass->allocate (actor, box, flags); + + /* update tooltip position */ + if (priv->tooltip) + { + in_v.x = in_v.y = in_v.z = 0; + clutter_actor_apply_transform_to_point (actor, &in_v, &out_v); + area.x = out_v.x; + area.y = out_v.y; + + in_v.x = box->x2 - box->x1; + in_v.y = box->y2 - box->y1; + clutter_actor_apply_transform_to_point (actor, &in_v, &out_v); + area.width = out_v.x - area.x; + area.height = out_v.y - area.y; + + st_tooltip_set_tip_area (priv->tooltip, &area); + } + + + + if (priv->border_image) + { + ClutterActorBox frame_box = { + 0, + 0, + box->x2 - box->x1, + box->y2 - box->y1 + }; + + clutter_actor_allocate (CLUTTER_ACTOR (priv->border_image), + &frame_box, + flags); + } + + if (priv->background_image) + { + ClutterActorBox frame_box = { + 0, 0, box->x2 - box->x1, box->y2 - box->y1 + }; + gfloat w, h; + + clutter_actor_get_size (CLUTTER_ACTOR (priv->background_image), &w, &h); + + /* scale the background into the allocated bounds */ + if (w > frame_box.x2 || h > frame_box.y2) + { + gint new_h, new_w, offset; + gint box_w, box_h; + + box_w = (int) frame_box.x2; + box_h = (int) frame_box.y2; + + /* scale to fit */ + new_h = (int)((h / w) * ((gfloat) box_w)); + new_w = (int)((w / h) * ((gfloat) box_h)); + + if (new_h > box_h) + { + /* center for new width */ + offset = ((box_w) - new_w) * 0.5; + frame_box.x1 = offset; + frame_box.x2 = offset + new_w; + + frame_box.y2 = box_h; + } + else + { + /* center for new height */ + offset = ((box_h) - new_h) * 0.5; + frame_box.y1 = offset; + frame_box.y2 = offset + new_h; + + frame_box.x2 = box_w; + } + + } + else + { + /* center the background on the widget */ + frame_box.x1 = (int)(((box->x2 - box->x1) / 2) - (w / 2)); + frame_box.y1 = (int)(((box->y2 - box->y1) / 2) - (h / 2)); + frame_box.x2 = frame_box.x1 + w; + frame_box.y2 = frame_box.y1 + h; + } + + clutter_actor_allocate (CLUTTER_ACTOR (priv->background_image), + &frame_box, + flags); + } +} + +static void +st_widget_real_draw_background (StWidget *self, + ClutterActor *background, + const ClutterColor *color) +{ + /* Default implementation just draws the background + * colour and the image on top + */ + if (color && color->alpha != 0) + { + ClutterActor *actor = CLUTTER_ACTOR (self); + ClutterActorBox allocation = { 0, }; + ClutterColor bg_color = *color; + gfloat w, h; + + bg_color.alpha = clutter_actor_get_paint_opacity (actor) + * bg_color.alpha + / 255; + + clutter_actor_get_allocation_box (actor, &allocation); + + w = allocation.x2 - allocation.x1; + h = allocation.y2 - allocation.y1; + + cogl_set_source_color4ub (bg_color.red, + bg_color.green, + bg_color.blue, + bg_color.alpha); + cogl_rectangle (0, 0, w, h); + } + + if (background) + clutter_actor_paint (background); +} + +static void +st_widget_paint (ClutterActor *self) +{ + StWidgetPrivate *priv = ST_WIDGET (self)->priv; + StWidgetClass *klass = ST_WIDGET_GET_CLASS (self); + + klass->draw_background (ST_WIDGET (self), + priv->border_image, + priv->bg_color); + + if (priv->background_image != NULL) + clutter_actor_paint (priv->background_image); +} + +static void +st_widget_parent_set (ClutterActor *widget, + ClutterActor *old_parent) +{ + ClutterActorClass *parent_class; + ClutterActor *new_parent; + + parent_class = CLUTTER_ACTOR_CLASS (st_widget_parent_class); + if (parent_class->parent_set) + parent_class->parent_set (widget, old_parent); + + new_parent = clutter_actor_get_parent (widget); + + /* don't send the style changed signal if we no longer have a parent actor */ + if (new_parent) + st_stylable_changed ((StStylable*) widget); +} + +static void +st_widget_map (ClutterActor *actor) +{ + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + + CLUTTER_ACTOR_CLASS (st_widget_parent_class)->map (actor); + + st_widget_ensure_style ((StWidget*) actor); + + if (priv->border_image) + clutter_actor_map (priv->border_image); + + if (priv->background_image) + clutter_actor_map (priv->background_image); + + if (priv->tooltip) + clutter_actor_map ((ClutterActor *) priv->tooltip); +} + +static void +st_widget_unmap (ClutterActor *actor) +{ + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + + CLUTTER_ACTOR_CLASS (st_widget_parent_class)->unmap (actor); + + if (priv->border_image) + clutter_actor_unmap (priv->border_image); + + if (priv->background_image) + clutter_actor_unmap (priv->background_image); + + if (priv->tooltip) + clutter_actor_unmap ((ClutterActor *) priv->tooltip); +} + +static void +st_widget_style_changed (StStylable *self) +{ + StWidgetPrivate *priv = ST_WIDGET (self)->priv; + StBorderImage *border_image = NULL; + StTextureCache *texture_cache; + ClutterTexture *texture; + gchar *bg_file = NULL; + StPadding *padding = NULL; + gboolean relayout_needed = FALSE; + gboolean has_changed = FALSE; + ClutterColor *color; + + /* application has request this widget is not stylable */ + if (!priv->is_stylable) + return; + + /* cache these values for use in the paint function */ + st_stylable_get (self, + "background-color", &color, + "background-image", &bg_file, + "border-image", &border_image, + "padding", &padding, + NULL); + + if (color) + { + if (priv->bg_color && clutter_color_equal (color, priv->bg_color)) + { + /* color is the same ... */ + clutter_color_free (color); + } + else + { + clutter_color_free (priv->bg_color); + priv->bg_color = color; + has_changed = TRUE; + } + } + else + if (priv->bg_color) + { + clutter_color_free (priv->bg_color); + priv->bg_color = NULL; + has_changed = TRUE; + } + + + + if (padding) + { + if (priv->padding.top != padding->top || + priv->padding.left != padding->left || + priv->padding.right != padding->right || + priv->padding.bottom != padding->bottom) + { + /* Padding changed. Need to relayout. */ + has_changed = TRUE; + relayout_needed = TRUE; + } + + priv->padding = *padding; + g_boxed_free (ST_TYPE_PADDING, padding); + } + + if (priv->border_image) + { + clutter_actor_unparent (priv->border_image); + priv->border_image = NULL; + } + + if (priv->background_image) + { + clutter_actor_unparent (priv->background_image); + priv->background_image = NULL; + } + + texture_cache = st_texture_cache_get_default (); + + /* Check if the URL is actually present, not garbage in the property */ + if (border_image && border_image->uri) + { + gint border_left, border_right, border_top, border_bottom; + gint width, height; + + /* `border-image' takes precedence over `background-image'. + * Firefox lets the background-image shine thru when border-image has + * alpha an channel, maybe that would be an option for the future. */ + texture = st_texture_cache_get_texture (texture_cache, + border_image->uri); + + clutter_texture_get_base_size (CLUTTER_TEXTURE (texture), + &width, &height); + + border_left = ccss_position_get_size (&border_image->left, width); + border_top = ccss_position_get_size (&border_image->top, height); + border_right = ccss_position_get_size (&border_image->right, width); + border_bottom = ccss_position_get_size (&border_image->bottom, height); + + priv->border_image = st_texture_frame_new (texture, + border_top, + border_right, + border_bottom, + border_left); + clutter_actor_set_parent (priv->border_image, CLUTTER_ACTOR (self)); + g_boxed_free (ST_TYPE_BORDER_IMAGE, border_image); + + has_changed = TRUE; + relayout_needed = TRUE; + } + + if (bg_file != NULL && + strcmp (bg_file, "none")) + { + texture = st_texture_cache_get_texture (texture_cache, bg_file); + priv->background_image = (ClutterActor*) texture; + + if (priv->background_image != NULL) + { + clutter_actor_set_parent (priv->background_image, + CLUTTER_ACTOR (self)); + } + else + g_warning ("Could not load %s", bg_file); + + has_changed = TRUE; + relayout_needed = TRUE; + } + g_free (bg_file); + + /* If there are any properties above that need to cause a relayout thay + * should set this flag. + */ + if (has_changed) + { + if (relayout_needed) + clutter_actor_queue_relayout ((ClutterActor *) self); + else + clutter_actor_queue_redraw ((ClutterActor *) self); + } + + priv->is_style_dirty = FALSE; +} + +static void +st_widget_stylable_child_notify (ClutterActor *actor, + gpointer user_data) +{ + if (ST_IS_STYLABLE (actor)) + st_stylable_changed ((StStylable*) actor); +} + +static void +st_widget_stylable_changed (StStylable *stylable) +{ + + ST_WIDGET (stylable)->priv->is_style_dirty = TRUE; + + /* update the style only if we are mapped */ + if (!CLUTTER_ACTOR_IS_MAPPED ((ClutterActor *) stylable)) + return; + + g_signal_emit_by_name (stylable, "style-changed", 0); + + + if (CLUTTER_IS_CONTAINER (stylable)) + { + /* notify our children that their parent stylable has changed */ + clutter_container_foreach ((ClutterContainer *) stylable, + st_widget_stylable_child_notify, + NULL); + } +} + +static gboolean +st_widget_enter (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + + + if (priv->has_tooltip) + st_widget_show_tooltip ((StWidget*) actor); + + if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event) + return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event (actor, event); + else + return FALSE; +} + +static gboolean +st_widget_leave (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + + if (priv->has_tooltip) + st_tooltip_hide (priv->tooltip); + + if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event) + return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event (actor, event); + else + return FALSE; +} + +static void +st_widget_hide (ClutterActor *actor) +{ + StWidget *widget = (StWidget *) actor; + + /* hide the tooltip, if there is one */ + if (widget->priv->tooltip) + st_tooltip_hide (ST_TOOLTIP (widget->priv->tooltip)); + + CLUTTER_ACTOR_CLASS (st_widget_parent_class)->hide (actor); +} + + + +static void +st_widget_class_init (StWidgetClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (StWidgetPrivate)); + + gobject_class->set_property = st_widget_set_property; + gobject_class->get_property = st_widget_get_property; + gobject_class->dispose = st_widget_dispose; + gobject_class->finalize = st_widget_finalize; + + actor_class->allocate = st_widget_allocate; + actor_class->paint = st_widget_paint; + actor_class->parent_set = st_widget_parent_set; + actor_class->map = st_widget_map; + actor_class->unmap = st_widget_unmap; + + actor_class->enter_event = st_widget_enter; + actor_class->leave_event = st_widget_leave; + actor_class->hide = st_widget_hide; + + klass->draw_background = st_widget_real_draw_background; + + /** + * StWidget:pseudo-class: + * + * The pseudo-class of the actor. Typical values include "hover", "active", + * "focus". + */ + g_object_class_install_property (gobject_class, + PROP_PSEUDO_CLASS, + g_param_spec_string ("pseudo-class", + "Pseudo Class", + "Pseudo class for styling", + "", + ST_PARAM_READWRITE)); + /** + * StWidget:style-class: + * + * The style-class of the actor for use in styling. + */ + g_object_class_install_property (gobject_class, + PROP_STYLE_CLASS, + g_param_spec_string ("style-class", + "Style Class", + "Style class for styling", + "", + ST_PARAM_READWRITE)); + + g_object_class_override_property (gobject_class, PROP_STYLE, "style"); + + /** + * StWidget:stylable: + * + * Enable or disable styling of the widget + */ + pspec = g_param_spec_boolean ("stylable", + "Stylable", + "Whether the table should be styled", + TRUE, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_STYLABLE, + pspec); + + /** + * StWidget:has-tooltip: + * + * Determines whether the widget has a tooltip. If set to TRUE, causes the + * widget to monitor enter and leave events (i.e. sets the widget reactive). + */ + pspec = g_param_spec_boolean ("has-tooltip", + "Has Tooltip", + "Determines whether the widget has a tooltip", + FALSE, + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, + PROP_HAS_TOOLTIP, + pspec); + + + /** + * StWidget:tooltip-text: + * + * text displayed on the tooltip + */ + pspec = g_param_spec_string ("tooltip-text", + "Tooltip Text", + "Text displayed on the tooltip", + "", + ST_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_TOOLTIP_TEXT, pspec); + +} + +static StStyle * +st_widget_get_style (StStylable *stylable) +{ + StWidgetPrivate *priv = ST_WIDGET (stylable)->priv; + + return priv->style; +} + +static void +st_style_changed_cb (StStyle *style, + StStylable *stylable) +{ + st_stylable_changed (stylable); +} + + +static void +st_widget_set_style (StStylable *stylable, + StStyle *style) +{ + StWidgetPrivate *priv = ST_WIDGET (stylable)->priv; + + if (priv->style) + g_object_unref (priv->style); + + priv->style = g_object_ref_sink (style); + + g_signal_connect (priv->style, + "changed", + G_CALLBACK (st_style_changed_cb), + stylable); +} + +static StStylable* +st_widget_get_container (StStylable *stylable) +{ + ClutterActor *parent; + + g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL); + + parent = clutter_actor_get_parent (CLUTTER_ACTOR (stylable)); + + if (ST_IS_STYLABLE (parent)) + return ST_STYLABLE (parent); + else + return NULL; +} + +static StStylable* +st_widget_get_base_style (StStylable *stylable) +{ + return NULL; +} + +static const gchar* +st_widget_get_style_id (StStylable *stylable) +{ + g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL); + + return clutter_actor_get_name (CLUTTER_ACTOR (stylable)); +} + +static const gchar* +st_widget_get_style_type (StStylable *stylable) +{ + return G_OBJECT_TYPE_NAME (stylable); +} + +static const gchar* +st_widget_get_style_class (StStylable *stylable) +{ + g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL); + + return ST_WIDGET (stylable)->priv->style_class; +} + +static const gchar* +st_widget_get_pseudo_class (StStylable *stylable) +{ + g_return_val_if_fail (ST_IS_WIDGET (stylable), NULL); + + return ST_WIDGET (stylable)->priv->pseudo_class; +} + +static gboolean +st_widget_get_viewport (StStylable *stylable, + gint *x, + gint *y, + gint *width, + gint *height) +{ + g_return_val_if_fail (ST_IS_WIDGET (stylable), FALSE); + + *x = 0; + *y = 0; + + *width = clutter_actor_get_width (CLUTTER_ACTOR (stylable)); + *height = clutter_actor_get_height (CLUTTER_ACTOR (stylable)); + + return TRUE; +} + +/** + * st_widget_set_style_class_name: + * @actor: a #StWidget + * @style_class: a new style class string + * + * Set the style class name + */ +void +st_widget_set_style_class_name (StWidget *actor, + const gchar *style_class) +{ + StWidgetPrivate *priv = actor->priv; + + g_return_if_fail (ST_WIDGET (actor)); + + priv = actor->priv; + + if (g_strcmp0 (style_class, priv->style_class)) + { + g_free (priv->style_class); + priv->style_class = g_strdup (style_class); + + st_stylable_changed ((StStylable*) actor); + + g_object_notify (G_OBJECT (actor), "style-class"); + } +} + + +/** + * st_widget_get_style_class_name: + * @actor: a #StWidget + * + * Get the current style class name + * + * Returns: the class name string. The string is owned by the #StWidget and + * should not be modified or freed. + */ +const gchar* +st_widget_get_style_class_name (StWidget *actor) +{ + g_return_val_if_fail (ST_WIDGET (actor), NULL); + + return actor->priv->style_class; +} + +/** + * st_widget_get_style_pseudo_class: + * @actor: a #StWidget + * + * Get the current style pseudo class + * + * Returns: the pseudo class string. The string is owned by the #StWidget and + * should not be modified or freed. + */ +const gchar* +st_widget_get_style_pseudo_class (StWidget *actor) +{ + g_return_val_if_fail (ST_WIDGET (actor), NULL); + + return actor->priv->pseudo_class; +} + +/** + * st_widget_set_style_pseudo_class: + * @actor: a #StWidget + * @pseudo_class: a new pseudo class string + * + * Set the style pseudo class + */ +void +st_widget_set_style_pseudo_class (StWidget *actor, + const gchar *pseudo_class) +{ + StWidgetPrivate *priv; + + g_return_if_fail (ST_WIDGET (actor)); + + priv = actor->priv; + + if (g_strcmp0 (pseudo_class, priv->pseudo_class)) + { + g_free (priv->pseudo_class); + priv->pseudo_class = g_strdup (pseudo_class); + + st_stylable_changed ((StStylable*) actor); + + g_object_notify (G_OBJECT (actor), "pseudo-class"); + } +} + + +static void +st_stylable_iface_init (StStylableIface *iface) +{ + static gboolean is_initialized = FALSE; + + if (!is_initialized) + { + GParamSpec *pspec; + ClutterColor color = { 0x00, 0x00, 0x00, 0xff }; + ClutterColor bg_color = { 0xff, 0xff, 0xff, 0x00 }; + + is_initialized = TRUE; + + pspec = clutter_param_spec_color ("background-color", + "Background Color", + "The background color of an actor", + &bg_color, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec); + + pspec = clutter_param_spec_color ("color", + "Text Color", + "The color of the text of an actor", + &color, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec); + + pspec = g_param_spec_string ("background-image", + "Background Image", + "Background image filename", + NULL, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec); + + pspec = g_param_spec_string ("font-family", + "Font Family", + "Name of the font to use", + "Sans", + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec); + + pspec = g_param_spec_int ("font-size", + "Font Size", + "Size of the font to use in pixels", + 0, G_MAXINT, 12, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec); + + pspec = g_param_spec_boxed ("border-image", + "Border image", + "9-slice image to use for drawing borders and background", + ST_TYPE_BORDER_IMAGE, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec); + + pspec = g_param_spec_boxed ("padding", + "Padding", + "Padding between the widget's borders " + "and its content", + ST_TYPE_PADDING, + G_PARAM_READWRITE); + st_stylable_iface_install_property (iface, ST_TYPE_WIDGET, pspec); + + iface->style_changed = st_widget_style_changed; + iface->stylable_changed = st_widget_stylable_changed; + + iface->get_style = st_widget_get_style; + iface->set_style = st_widget_set_style; + iface->get_base_style = st_widget_get_base_style; + iface->get_container = st_widget_get_container; + iface->get_style_id = st_widget_get_style_id; + iface->get_style_type = st_widget_get_style_type; + iface->get_style_class = st_widget_get_style_class; + iface->get_pseudo_class = st_widget_get_pseudo_class; + /* iface->get_attribute = st_widget_get_attribute; */ + iface->get_viewport = st_widget_get_viewport; + } +} + + +static void +st_widget_name_notify (StWidget *widget, + GParamSpec *pspec, + gpointer data) +{ + st_stylable_changed ((StStylable*) widget); +} + +static void +st_widget_init (StWidget *actor) +{ + StWidgetPrivate *priv; + + actor->priv = priv = ST_WIDGET_GET_PRIVATE (actor); + priv->is_stylable = TRUE; + + /* connect style changed */ + g_signal_connect (actor, "notify::name", G_CALLBACK (st_widget_name_notify), NULL); + + /* set the default style */ + st_widget_set_style (ST_STYLABLE (actor), st_style_get_default ()); + +} + +static StBorderImage * +st_border_image_copy (const StBorderImage *border_image) +{ + StBorderImage *copy; + + g_return_val_if_fail (border_image != NULL, NULL); + + copy = g_slice_new (StBorderImage); + *copy = *border_image; + + return copy; +} + +static void +st_border_image_free (StBorderImage *border_image) +{ + if (G_LIKELY (border_image)) + g_slice_free (StBorderImage, border_image); +} + +GType +st_border_image_get_type (void) +{ + static GType our_type = 0; + + if (G_UNLIKELY (our_type == 0)) + our_type = + g_boxed_type_register_static (I_("StBorderImage"), + (GBoxedCopyFunc) st_border_image_copy, + (GBoxedFreeFunc) st_border_image_free); + + return our_type; +} + +/** + * st_widget_ensure_style: + * @widget: A #StWidget + * + * Ensures that @widget has read its style information. + * + */ +void +st_widget_ensure_style (StWidget *widget) +{ + g_return_if_fail (ST_IS_WIDGET (widget)); + + if (widget->priv->is_style_dirty) + { + g_signal_emit_by_name (widget, "style-changed", 0); + } +} + + +/** + * st_widget_get_border_image: + * @actor: A #StWidget + * + * Get the texture used as the border image. This is set using the + * "border-image" CSS property. This function should normally only be used + * by subclasses. + * + * Returns: #ClutterActor + */ +ClutterActor * +st_widget_get_border_image (StWidget *actor) +{ + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + return priv->border_image; +} + +/** + * st_widget_get_background_image: + * @actor: A #StWidget + * + * Get the texture used as the background image. This is set using the + * "background-image" CSS property. This function should normally only be used + * by subclasses. + * + * Returns: a #ClutterActor + */ +ClutterActor * +st_widget_get_background_image (StWidget *actor) +{ + StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + return priv->background_image; +} + +/** + * st_widget_get_padding: + * @widget: A #StWidget + * @padding: A pointer to an #StPadding to fill + * + * Gets the padding of the widget, set using the "padding" CSS property. This + * function should normally only be used by subclasses. + * + */ +void +st_widget_get_padding (StWidget *widget, + StPadding *padding) +{ + g_return_if_fail (ST_IS_WIDGET (widget)); + g_return_if_fail (padding != NULL); + + *padding = widget->priv->padding; +} + +/** + * st_widget_set_has_tooltip: + * @widget: A #StWidget + * @has_tooltip: #TRUE if the widget should display a tooltip + * + * Enables tooltip support on the #StWidget. + * + * Note that setting has-tooltip to #TRUE will cause the widget to be set + * reactive. If you no longer need tooltip support and do not need the widget + * to be reactive, you need to set ClutterActor::reactive to FALSE. + * + */ +void +st_widget_set_has_tooltip (StWidget *widget, + gboolean has_tooltip) +{ + StWidgetPrivate *priv; + + g_return_if_fail (ST_IS_WIDGET (widget)); + + priv = widget->priv; + + priv->has_tooltip = has_tooltip; + + if (has_tooltip) + { + clutter_actor_set_reactive ((ClutterActor*) widget, TRUE); + + if (!priv->tooltip) + { + priv->tooltip = g_object_new (ST_TYPE_TOOLTIP, NULL); + clutter_actor_set_parent ((ClutterActor *) priv->tooltip, + (ClutterActor *) widget); + } + } + else + { + if (priv->tooltip) + { + clutter_actor_unparent (CLUTTER_ACTOR (priv->tooltip)); + priv->tooltip = NULL; + } + } +} + +/** + * st_widget_get_has_tooltip: + * @widget: A #StWidget + * + * Returns the current value of the has-tooltip property. See + * st_tooltip_set_has_tooltip() for more information. + * + * Returns: current value of has-tooltip on @widget + */ +gboolean +st_widget_get_has_tooltip (StWidget *widget) +{ + g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE); + + return widget->priv->has_tooltip; +} + +/** + * st_widget_set_tooltip_text: + * @widget: A #StWidget + * @text: text to set as the tooltip + * + * Set the tooltip text of the widget. This will set StWidget::has-tooltip to + * #TRUE. A value of #NULL will unset the tooltip and set has-tooltip to #FALSE. + * + */ +void +st_widget_set_tooltip_text (StWidget *widget, + const gchar *text) +{ + StWidgetPrivate *priv; + + g_return_if_fail (ST_IS_WIDGET (widget)); + + priv = widget->priv; + + if (text == NULL) + st_widget_set_has_tooltip (widget, FALSE); + else + st_widget_set_has_tooltip (widget, TRUE); + + st_tooltip_set_label (priv->tooltip, text); +} + +/** + * st_widget_get_tooltip_text: + * @widget: A #StWidget + * + * Get the current tooltip string + * + * Returns: The current tooltip string, owned by the #StWidget + */ +const gchar* +st_widget_get_tooltip_text (StWidget *widget) +{ + StWidgetPrivate *priv; + + g_return_val_if_fail (ST_IS_WIDGET (widget), NULL); + priv = widget->priv; + + if (!priv->has_tooltip) + return NULL; + + return st_tooltip_get_label (widget->priv->tooltip); +} + +/** + * st_widget_show_tooltip: + * @widget: A #StWidget + * + * Show the tooltip for @widget + * + */ +void +st_widget_show_tooltip (StWidget *widget) +{ + gfloat x, y, width, height; + ClutterGeometry area; + + g_return_if_fail (ST_IS_WIDGET (widget)); + + /* XXX not necceary, but first allocate transform is wrong */ + + clutter_actor_get_transformed_position ((ClutterActor*) widget, + &x, &y); + + clutter_actor_get_size ((ClutterActor*) widget, &width, &height); + + area.x = x; + area.y = y; + area.width = width; + area.height = height; + + + if (widget->priv->tooltip) + { + st_tooltip_set_tip_area (widget->priv->tooltip, &area); + st_tooltip_show (widget->priv->tooltip); + } +} + +/** + * st_widget_hide_tooltip: + * @widget: A #StWidget + * + * Hide the tooltip for @widget + * + */ +void +st_widget_hide_tooltip (StWidget *widget) +{ + g_return_if_fail (ST_IS_WIDGET (widget)); + + if (widget->priv->tooltip) + st_tooltip_hide (widget->priv->tooltip); +} + +/** + * st_widget_draw_background: + * @widget: a #StWidget + * + * Invokes #StWidget::draw_background() using the default background + * image and/or color from the @widget style + * + * This function should be used by subclasses of #StWidget that override + * the paint() virtual function and cannot chain up + */ +void +st_widget_draw_background (StWidget *self) +{ + StWidgetPrivate *priv; + StWidgetClass *klass; + + g_return_if_fail (ST_IS_WIDGET (self)); + + priv = self->priv; + + klass = ST_WIDGET_GET_CLASS (self); + klass->draw_background (ST_WIDGET (self), + priv->border_image, + priv->bg_color); + +} diff --git a/src/st/st-widget.h b/src/st/st-widget.h new file mode 100644 index 000000000..1c5ee3697 --- /dev/null +++ b/src/st/st-widget.h @@ -0,0 +1,108 @@ +/* + * st-widget.h: Base class for St actors + * + * Copyright 2007 OpenedHand + * Copyright 2008, 2009 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * Boston, MA 02111-1307, USA. + * + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_WIDGET_H__ +#define __ST_WIDGET_H__ + +#include +#include + +G_BEGIN_DECLS + +#define ST_TYPE_WIDGET (st_widget_get_type ()) +#define ST_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_WIDGET, StWidget)) +#define ST_IS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_WIDGET)) +#define ST_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_WIDGET, StWidgetClass)) +#define ST_IS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_WIDGET)) +#define ST_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_WIDGET, StWidgetClass)) + +typedef struct _StWidget StWidget; +typedef struct _StWidgetPrivate StWidgetPrivate; +typedef struct _StWidgetClass StWidgetClass; + +/** + * StWidget: + * + * Base class for stylable actors. The contents of the #StWidget + * structure are private and should only be accessed through the + * public API. + */ +struct _StWidget +{ + /*< private >*/ + ClutterActor parent_instance; + + StWidgetPrivate *priv; +}; + +/** + * StWidgetClass: + * + * Base class for stylable actors. + */ +struct _StWidgetClass +{ + /*< private >*/ + ClutterActorClass parent_class; + + /* vfuncs */ + void (* draw_background) (StWidget *self, + ClutterActor *background, + const ClutterColor *color); +}; + +GType st_widget_get_type (void) G_GNUC_CONST; + +void st_widget_set_style_pseudo_class (StWidget *actor, + const gchar *pseudo_class); +G_CONST_RETURN gchar *st_widget_get_style_pseudo_class (StWidget *actor); +void st_widget_set_style_class_name (StWidget *actor, + const gchar *style_class); +G_CONST_RETURN gchar *st_widget_get_style_class_name (StWidget *actor); + +void st_widget_set_has_tooltip (StWidget *widget, + gboolean has_tooltip); +gboolean st_widget_get_has_tooltip (StWidget *widget); +void st_widget_set_tooltip_text (StWidget *widget, + const gchar *text); +const gchar* st_widget_get_tooltip_text (StWidget *widget); + +void st_widget_show_tooltip (StWidget *widget); +void st_widget_hide_tooltip (StWidget *widget); + +void st_widget_ensure_style (StWidget *widget); + + +/* Only to be used by sub-classes of StWidget */ +ClutterActor *st_widget_get_background_image (StWidget *actor); +ClutterActor *st_widget_get_border_image (StWidget *actor); +void st_widget_get_padding (StWidget *widget, + StPadding *padding); +void st_widget_draw_background (StWidget *widget); + +G_END_DECLS + +#endif /* __ST_WIDGET_H__ */