From f8db7689af3b3d74338a2cbc4c2b5d7fd986d005 Mon Sep 17 00:00:00 2001 From: Owen Taylor Date: Wed, 12 Nov 2008 21:09:27 +0000 Subject: [PATCH] Experimentally add build infrastructure and a few Tidy widgets For experimenting with using tidy, import TidyButton and TidyGrid (+ dependencies) into our source tree and set up build machinery to build them and build a typelib for them. The sources are build right into libgnome-shell.so, so the Shell.gir and Tidy.gir actually point to the same shared library. src/Makefile-tidy.am: Build libtidy-1.0.la src/Makefile.am: Include built tidy into gnome-shell.la and build Tidy-1.0.typelib src/tidy/*: Add some source files from Tidy svn path=/trunk/; revision=42 --- src/Makefile-tidy.am | 109 ++++ src/Makefile.am | 41 +- src/tidy/tidy-actor.c | 529 +++++++++++++++++ src/tidy/tidy-actor.h | 88 +++ src/tidy/tidy-button.c | 374 ++++++++++++ src/tidy/tidy-button.h | 70 +++ src/tidy/tidy-debug.h | 4 + src/tidy/tidy-enum-types.c.in | 30 + src/tidy/tidy-enum-types.h.in | 25 + src/tidy/tidy-frame.c | 515 +++++++++++++++++ src/tidy/tidy-frame.h | 64 +++ src/tidy/tidy-grid.c | 1005 +++++++++++++++++++++++++++++++++ src/tidy/tidy-grid.h | 99 ++++ src/tidy/tidy-marshal.list | 8 + src/tidy/tidy-private.h | 40 ++ src/tidy/tidy-stylable.c | 789 ++++++++++++++++++++++++++ src/tidy/tidy-stylable.h | 69 +++ src/tidy/tidy-style.c | 653 +++++++++++++++++++++ src/tidy/tidy-style.h | 85 +++ src/tidy/tidy-types.h | 25 + src/tidy/tidy-util.c | 37 ++ src/tidy/tidy-util.h | 9 + 22 files changed, 4661 insertions(+), 7 deletions(-) create mode 100644 src/Makefile-tidy.am create mode 100644 src/tidy/tidy-actor.c create mode 100644 src/tidy/tidy-actor.h create mode 100644 src/tidy/tidy-button.c create mode 100644 src/tidy/tidy-button.h create mode 100644 src/tidy/tidy-debug.h create mode 100644 src/tidy/tidy-enum-types.c.in create mode 100644 src/tidy/tidy-enum-types.h.in create mode 100644 src/tidy/tidy-frame.c create mode 100644 src/tidy/tidy-frame.h create mode 100644 src/tidy/tidy-grid.c create mode 100644 src/tidy/tidy-grid.h create mode 100644 src/tidy/tidy-marshal.list create mode 100644 src/tidy/tidy-private.h create mode 100644 src/tidy/tidy-stylable.c create mode 100644 src/tidy/tidy-stylable.h create mode 100644 src/tidy/tidy-style.c create mode 100644 src/tidy/tidy-style.h create mode 100644 src/tidy/tidy-types.h create mode 100644 src/tidy/tidy-util.c create mode 100644 src/tidy/tidy-util.h diff --git a/src/Makefile-tidy.am b/src/Makefile-tidy.am new file mode 100644 index 000000000..c82e74b16 --- /dev/null +++ b/src/Makefile-tidy.am @@ -0,0 +1,109 @@ +NULL = + +GLIB_GENMARSHAL = `pkg-config --variable=glib_genmarshal glib-2.0` +GLIB_MKENUMS = `pkg-config --variable=glib_mkenums glib-2.0` + +tidy_cflags = \ + -I$(top_srcdir)/src \ + -DPREFIX=\""$(prefix)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DG_DISABLE_DEPRECATED \ + -DG_LOG_DOMAIN=\"Tidy\" \ + $(MUTTER_PLUGIN_CFLAGS) \ + $(NULL) + +tidy_built_sources = \ + tidy-enum-types.h \ + tidy-enum-types.c \ + tidy-marshal.h \ + tidy-marshal.c + +BUILT_SOURCES += $(tidy_built_sources) + +STAMP_FILES = stamp-tidy-marshal.h stamp-tidy-enum-types.h + +# please, keep this sorted alphabetically +tidy_source_h = \ + tidy/tidy-actor.h \ + tidy/tidy-button.h \ + tidy/tidy-frame.h \ + tidy/tidy-grid.h \ + tidy/tidy-stylable.h \ + tidy/tidy-style.h \ + tidy/tidy-types.h \ + tidy/tidy-util.h \ + $(NULL) + +tidy_source_h_private = \ + tidy/tidy-debug.h \ + $(NULL) + +# please, keep this sorted alphabetically +tidy_source_c = \ + tidy/tidy-actor.c \ + tidy/tidy-button.c \ + tidy/tidy-frame.c \ + tidy/tidy-grid.c \ + tidy/tidy-stylable.c \ + tidy/tidy-style.c \ + tidy/tidy-util.c \ + $(NULL) + +tidy-marshal.h: stamp-tidy-marshal.h + @true +stamp-tidy-marshal.h: Makefile tidy/tidy-marshal.list + $(GLIB_GENMARSHAL) \ + --prefix=_tidy_marshal \ + --header \ + $(srcdir)/tidy/tidy-marshal.list > xgen-tmh && \ + (cmp -s xgen-tmh tidy-marshal.h || cp -f xgen-tmh tidy-marshal.h) && \ + rm -f xgen-tmh && \ + echo timestamp > $(@F) + +tidy-marshal.c: Makefile tidy/tidy-marshal.list + (echo "#include \"tidy-marshal.h\"" ; \ + $(GLIB_GENMARSHAL) \ + --prefix=_tidy_marshal \ + --body \ + $(srcdir)/tidy/tidy-marshal.list ) > xgen-tmc && \ + cp -f xgen-tmc tidy-marshal.c && \ + rm -f xgen-tmc + +tidy-enum-types.h: stamp-tidy-enum-types.h Makefile + @true +stamp-tidy-enum-types.h: $(tidy_source_h) tidy/tidy-enum-types.h.in + ( cd $(srcdir) && \ + $(GLIB_MKENUMS) \ + --template $(srcdir)/tidy/tidy-enum-types.h.in \ + $(tidy_source_h) ) >> xgen-teth && \ + (cmp xgen-teth tidy-enum-types.h || cp xgen-teth tidy-enum-types.h) && \ + rm -f xgen-teth && \ + echo timestamp > $(@F) + +tidy-enum-types.c: stamp-tidy-enum-types.h tidy/tidy-enum-types.c.in + ( cd $(srcdir) && \ + $(GLIB_MKENUMS) \ + --template $(srcdir)/tidy/tidy-enum-types.c.in \ + $(tidy_source_h) ) >> xgen-tetc && \ + cp xgen-tetc tidy-enum-types.c && \ + rm -f xgen-tetc + +lib_LTLIBRARIES = libtidy-1.0.la + +libtidy_1_0_la_LIBADD = $(TIDY_LIBS) +libtidy_1_0_la_SOURCES = \ + $(tidy_source_c) \ + $(tidy_source_h) \ + $(tidy_source_h_priv) \ + $(tidy_built_sources) \ + $(NULL) +libtidy_1_0_la_CPPFLAGS = $(tidy_cflags) +libtidy_1_0_la_LDFLAGS = $(LDADD) + +CLEANFILES += $(STAMP_FILES) $(BUILT_SOURCES) + +EXTRA_DIST = \ + tidy/tidy-enum-types.h.in \ + tidy/tidy-enum-types.c.in \ + tidy/tidy-private.h \ + tidy/tidy-marshal.list diff --git a/src/Makefile.am b/src/Makefile.am index dc7fdc915..12859df41 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,9 @@ -INCLUDES = \ +BUILT_SOURCES = +CLEANFILES = + +include Makefile-tidy.am + +gnome_shell_cflags = \ $(MUTTER_PLUGIN_CFLAGS) \ -DGETTEXT_PACKAGE=gnome-shell \ -DJSDIR=\"$(pkgdatadir)/js\" @@ -14,7 +19,8 @@ libgnome_shell_la_SOURCES = \ shell-global.h libgnome_shell_la_LDFLAGS = -avoid-version -module -libgnome_shell_la_LIBADD = $(MUTTER_PLUGIN_LIBS) +libgnome_shell_la_LIBADD = $(MUTTER_PLUGIN_LIBS) libtidy-1.0.la +libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) # We can't have any undefined symbols when g-ir-scanner dlopens the library # to introspect it, so we link everything a _second_ time, including a @@ -27,14 +33,15 @@ libgnome_shell_introspect_la_SOURCES = \ # The dummy -rpath here is needed to convince libtool to build a # noinst_LTLIBRARY shared libgnome_shell_introspect_la_LDFLAGS = -avoid-version -module -rpath $(libdir) -libgnome_shell_introspect_la_LIBADD = $(MUTTER_PLUGIN_LIBS) +libgnome_shell_introspect_la_LIBADD = $(MUTTER_PLUGIN_LIBS) libtidy-1.0.la +libgnome_shell_introspect_la_CPPFLAGS = $(gnome_shell_cflags) typelibdir = $(pkglibdir)/girepository -typelib_DATA = Shell-0.1.typelib +typelib_DATA = Shell-0.1.typelib Tidy-1.0.typelib # After we run g-ir-scanner, we need to change the library name written in # the .gir file from the "fake" second copy of the library to the real name -Shell-0.1.gir: libgnome-shell-introspect.la $(libgnome_shell_la_SOURCES) +Shell-0.1.gir: libgnome-shell-introspect.la $(libgnome_shell_la_SOURCES) Makefile g-ir-scanner \ --namespace=Shell \ --nsversion=0.1 \ @@ -42,8 +49,8 @@ Shell-0.1.gir: libgnome-shell-introspect.la $(libgnome_shell_la_SOURCES) --include=Clutter-0.8 \ --include=Meta-2.25 \ --library=gnome-shell-introspect \ - $(libgnome_shell_la_SOURCES) \ - $(INCLUDES) \ +- $(libgnome_shell_la_SOURCES) \ + $(libgnome_shell_la_CPPFLAGS) \ -o $@.tmp sed 's/gnome-shell-introspect/gnome-shell/' < $@.tmp > $@ && rm $@.tmp @@ -51,3 +58,23 @@ Shell-0.1.gir: libgnome-shell-introspect.la $(libgnome_shell_la_SOURCES) # (not the fake library, since we've already done the rewriting) Shell-0.1.typelib: libgnome-shell.la Shell-0.1.gir LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler Shell-0.1.gir -o $@ + +# After we run g-ir-scanner, we need to change the library name written in +# the .gir file from the "fake" second copy of the library to the real name +Tidy-1.0.gir: libgnome-shell-introspect.la $(libgnome_shell_la_SOURCES) Makefile + g-ir-scanner \ + --namespace=Tidy \ + --nsversion=1.0 \ + --include=GObject-2.0 \ + --include=Clutter-0.8 \ + --library=gnome-shell-introspect \ + $(tidy_source_h) \ + $(tidy_source_c) \ + $(tidy_cflags) \ + -o $@.tmp + sed 's/gnome-shell-introspect/gnome-shell/' < $@.tmp > $@ && rm $@.tmp + +# 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) +Tidy-1.0.typelib: libgnome-shell.la Tidy-1.0.gir + LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler Tidy-1.0.gir -o $@ diff --git a/src/tidy/tidy-actor.c b/src/tidy/tidy-actor.c new file mode 100644 index 000000000..5f1298244 --- /dev/null +++ b/src/tidy/tidy-actor.c @@ -0,0 +1,529 @@ +/* tidy-actor.c: Base class for Tidy actors + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:tidy-actor + * @short_description: Base class for stylable actors + * + * #TidyActor is a simple abstract class on top of #ClutterActor. It + * provides basic themeing properties, support for padding and alignment. + * + * Actors in the Tidy library should subclass #TidyActor if they plan + * to obey to a certain #TidyStyle or if they implement #ClutterContainer + * and want to offer basic layout capabilities. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "tidy-actor.h" + +#include "tidy-debug.h" +#include "tidy-marshal.h" +#include "tidy-private.h" +#include "tidy-stylable.h" + +enum +{ + PROP_0, + + PROP_STYLE, + PROP_PADDING, + PROP_X_ALIGN, + PROP_Y_ALIGN +}; + +enum +{ + LAST_SIGNAL +}; + +static void tidy_stylable_iface_init (TidyStylableIface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TidyActor, tidy_actor, CLUTTER_TYPE_ACTOR, + G_IMPLEMENT_INTERFACE (TIDY_TYPE_STYLABLE, + tidy_stylable_iface_init)); + +#define TIDY_ACTOR_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TIDY_TYPE_ACTOR, TidyActorPrivate)) + +struct _TidyActorPrivate +{ + TidyStyle *style; + + TidyPadding padding; + + ClutterFixed x_align; + ClutterFixed y_align; +}; + +static void +tidy_actor_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TidyActor *actor = TIDY_ACTOR (gobject); + + switch (prop_id) + { + case PROP_PADDING: + tidy_actor_set_padding (actor, g_value_get_boxed (value)); + break; + + case PROP_X_ALIGN: + actor->priv->x_align = + CLUTTER_FIXED_TO_FLOAT (g_value_get_double (value)); + break; + + case PROP_Y_ALIGN: + actor->priv->y_align = + CLUTTER_FIXED_TO_FLOAT (g_value_get_double (value)); + break; + + case PROP_STYLE: + tidy_stylable_set_style (TIDY_STYLABLE (actor), + g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +tidy_actor_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TidyActor *actor = TIDY_ACTOR (gobject); + TidyActorPrivate *priv = actor->priv; + + switch (prop_id) + { + case PROP_PADDING: + { + TidyPadding padding = { 0, }; + + tidy_actor_get_padding (actor, &padding); + g_value_set_boxed (value, &padding); + } + break; + + case PROP_X_ALIGN: + g_value_set_double (value, CLUTTER_FIXED_TO_FLOAT (priv->x_align)); + break; + + case PROP_Y_ALIGN: + g_value_set_double (value, CLUTTER_FIXED_TO_FLOAT (priv->y_align)); + break; + + case PROP_STYLE: + g_value_set_object (value, priv->style); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +tidy_actor_dispose (GObject *gobject) +{ + TidyActor *actor = TIDY_ACTOR (gobject); + + if (actor->priv->style) + { + g_object_unref (actor->priv->style); + actor->priv->style = NULL; + } + + G_OBJECT_CLASS (tidy_actor_parent_class)->dispose (gobject); +} + +static void +tidy_actor_class_init (TidyActorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TidyActorPrivate)); + + gobject_class->set_property = tidy_actor_set_property; + gobject_class->get_property = tidy_actor_get_property; + gobject_class->dispose = tidy_actor_dispose; + + /** + * TidyActor:padding: + * + * Padding around an actor, expressed in #ClutterUnits. Padding + * is the internal space between an actors bounding box and its internal + * children. + */ + g_object_class_install_property (gobject_class, + PROP_PADDING, + g_param_spec_boxed ("padding", + "Padding", + "Units of padding around an actor", + TIDY_TYPE_PADDING, + TIDY_PARAM_READWRITE)); + /** + * TidyActor:x-align: + * + * Alignment of internal children along the X axis, relative to the + * actor's bounding box origin, and in relative units (1.0 is the + * current width of the actor). + * + * A value of 0.0 will left-align the children; 0.5 will align them at + * the middle of the actor's width; 1.0 will right align the children. + */ + g_object_class_install_property (gobject_class, + PROP_X_ALIGN, + g_param_spec_double ("x-align", + "X Alignment", + "Alignment (between 0.0 and 1.0) on the X axis", + 0.0, 1.0, 0.5, + TIDY_PARAM_READWRITE)); + /** + * TidyActor:y-align: + * + * Alignment of internal children along the Y axis, relative to the + * actor's bounding box origin, and in relative units (1.0 is the + * current height of the actor). + * + * A value of 0.0 will top-align the children; 0.5 will align them at + * the middle of the actor's height; 1.0 will bottom align the children. + */ + g_object_class_install_property (gobject_class, + PROP_Y_ALIGN, + g_param_spec_double ("y-align", + "Y Alignement", + "Alignment (between 0.0 and 1.0) on the Y axis", + 0.0, 1.0, 0.5, + TIDY_PARAM_READWRITE)); + g_object_class_override_property (gobject_class, PROP_STYLE, "style"); +} + +static TidyStyle * +tidy_actor_get_style (TidyStylable *stylable) +{ + TidyActorPrivate *priv = TIDY_ACTOR (stylable)->priv; + + if (!priv->style) + priv->style = g_object_ref (tidy_style_get_default ()); + + return priv->style; +} + +static void +tidy_actor_set_style (TidyStylable *stylable, + TidyStyle *style) +{ + TidyActorPrivate *priv = TIDY_ACTOR (stylable)->priv; + + if (priv->style) + g_object_unref (priv->style); + + priv->style = g_object_ref_sink (style); +} + +static void +tidy_stylable_iface_init (TidyStylableIface *iface) +{ + static gboolean is_initialized = FALSE; + + if (!is_initialized) + { + GParamSpec *pspec; + + pspec = g_param_spec_string ("font-name", + "Font Name", + "The font to use for displaying text", + "Sans 12px", + G_PARAM_READWRITE); + tidy_stylable_iface_install_property (iface, TIDY_TYPE_ACTOR, pspec); + + pspec = g_param_spec_boxed ("bg-color", + "Background Color", + "The background color of an actor", + CLUTTER_TYPE_COLOR, + G_PARAM_READWRITE); + tidy_stylable_iface_install_property (iface, TIDY_TYPE_ACTOR, pspec); + + pspec = g_param_spec_boxed ("active-color", + "Active Color", + "The color of an active actor", + CLUTTER_TYPE_COLOR, + G_PARAM_READWRITE); + tidy_stylable_iface_install_property (iface, TIDY_TYPE_ACTOR, pspec); + + pspec = g_param_spec_boxed ("text-color", + "Text Color", + "The color of the text of an actor", + CLUTTER_TYPE_COLOR, + G_PARAM_READWRITE); + tidy_stylable_iface_install_property (iface, TIDY_TYPE_ACTOR, pspec); + + iface->get_style = tidy_actor_get_style; + iface->set_style = tidy_actor_set_style; + } +} + +static void +tidy_actor_init (TidyActor *actor) +{ + TidyActorPrivate *priv; + + actor->priv = priv = TIDY_ACTOR_GET_PRIVATE (actor); + + /* no padding */ + priv->padding.top = priv->padding.bottom = 0; + priv->padding.right = priv->padding.left = 0; + + /* middle align */ + priv->x_align = priv->y_align = CLUTTER_FLOAT_TO_FIXED (0.5); + + clutter_actor_set_reactive (CLUTTER_ACTOR (actor), TRUE); +} + +/** + * tidy_actor_set_padding: + * @actor: a #TidyActor + * @padding: padding for internal children + * + * Sets @padding around @actor. + */ +void +tidy_actor_set_padding (TidyActor *actor, + const TidyPadding *padding) +{ + g_return_if_fail (TIDY_IS_ACTOR (actor)); + g_return_if_fail (padding != NULL); + + actor->priv->padding = *padding; + + g_object_notify (G_OBJECT (actor), "padding"); + + if (CLUTTER_ACTOR_IS_VISIBLE (actor)) + clutter_actor_queue_redraw (CLUTTER_ACTOR (actor)); +} + +/** + * tidy_actor_get_padding: + * @actor: a #TidyActor + * @padding: return location for the padding + * + * Retrieves the padding aound @actor. + */ +void +tidy_actor_get_padding (TidyActor *actor, + TidyPadding *padding) +{ + g_return_if_fail (TIDY_IS_ACTOR (actor)); + g_return_if_fail (padding != NULL); + + *padding = actor->priv->padding; +} + +/** + * tidy_actor_set_alignment: + * @actor: a #TidyActor + * @x_align: relative alignment on the X axis + * @y_align: relative alignment on the Y axis + * + * Sets the alignment, relative to the @actor's width and height, of + * the internal children. + */ +void +tidy_actor_set_alignment (TidyActor *actor, + gdouble x_align, + gdouble y_align) +{ + TidyActorPrivate *priv; + + g_return_if_fail (TIDY_IS_ACTOR (actor)); + + g_object_ref (actor); + g_object_freeze_notify (G_OBJECT (actor)); + + priv = actor->priv; + + x_align = CLAMP (x_align, 0.0, 1.0); + y_align = CLAMP (y_align, 0.0, 1.0); + + priv->x_align = CLUTTER_FLOAT_TO_FIXED (x_align); + g_object_notify (G_OBJECT (actor), "x-align"); + + priv->y_align = CLUTTER_FLOAT_TO_FIXED (y_align); + g_object_notify (G_OBJECT (actor), "y-align"); + + if (CLUTTER_ACTOR_IS_VISIBLE (actor)) + clutter_actor_queue_redraw (CLUTTER_ACTOR (actor)); + + g_object_thaw_notify (G_OBJECT (actor)); + g_object_unref (actor); +} + +/** + * tidy_actor_get_alignment: + * @actor: a #TidyActor + * @x_align: return location for the relative alignment on the X axis, + * or %NULL + * @y_align: return location for the relative alignment on the Y axis, + * or %NULL + * + * Retrieves the alignment, relative to the @actor's width and height, of + * the internal children. + */ +void +tidy_actor_get_alignment (TidyActor *actor, + gdouble *x_align, + gdouble *y_align) +{ + TidyActorPrivate *priv; + + g_return_if_fail (TIDY_IS_ACTOR (actor)); + + priv = actor->priv; + + if (x_align) + *x_align = CLUTTER_FIXED_TO_FLOAT (priv->x_align); + + if (y_align) + *y_align = CLUTTER_FIXED_TO_FLOAT (priv->y_align); +} + +/** + * tidy_actor_set_alignmentx: + * @actor: a #TidyActor + * @x_align: relative alignment on the X axis + * @y_align: relative alignment on the Y axis + * + * Fixed point version of tidy_actor_set_alignment(). + * + * Sets the alignment, relative to the @actor's width and height, of + * the internal children. + */ +void +tidy_actor_set_alignmentx (TidyActor *actor, + ClutterFixed x_align, + ClutterFixed y_align) +{ + TidyActorPrivate *priv; + + g_return_if_fail (TIDY_IS_ACTOR (actor)); + + g_object_ref (actor); + g_object_freeze_notify (G_OBJECT (actor)); + + priv = actor->priv; + + x_align = CLAMP (x_align, 0, CFX_ONE); + y_align = CLAMP (y_align, 0, CFX_ONE); + + if (priv->x_align != x_align) + { + priv->x_align = x_align; + g_object_notify (G_OBJECT (actor), "x-align"); + } + + if (priv->y_align != y_align) + { + priv->y_align = y_align; + g_object_notify (G_OBJECT (actor), "y-align"); + } + + if (CLUTTER_ACTOR_IS_VISIBLE (actor)) + clutter_actor_queue_redraw (CLUTTER_ACTOR (actor)); + + g_object_thaw_notify (G_OBJECT (actor)); + g_object_unref (actor); +} + +/** + * tidy_actor_get_alignmentx: + * @actor: a #TidyActor + * @x_align: return location for the relative alignment on the X axis, + * or %NULL + * @y_align: return location for the relative alignment on the Y axis, + * or %NULL + * + * Fixed point version of tidy_actor_get_alignment(). + * + * Retrieves the alignment, relative to the @actor's width and height, of + * the internal children. + */ +void +tidy_actor_get_alignmentx (TidyActor *actor, + ClutterFixed *x_align, + ClutterFixed *y_align) +{ + TidyActorPrivate *priv; + + g_return_if_fail (TIDY_IS_ACTOR (actor)); + + priv = actor->priv; + + if (x_align) + *x_align = priv->x_align; + + if (y_align) + *y_align = priv->y_align; +} + +static TidyPadding * +tidy_padding_copy (const TidyPadding *padding) +{ + TidyPadding *copy; + + g_return_val_if_fail (padding != NULL, NULL); + + copy = g_slice_new (TidyPadding); + *copy = *padding; + + return copy; +} + +static void +tidy_padding_free (TidyPadding *padding) +{ + if (G_LIKELY (padding)) + g_slice_free (TidyPadding, padding); +} + +GType +tidy_padding_get_type (void) +{ + static GType our_type = 0; + + if (G_UNLIKELY (our_type == 0)) + our_type = + g_boxed_type_register_static (I_("TidyPadding"), + (GBoxedCopyFunc) tidy_padding_copy, + (GBoxedFreeFunc) tidy_padding_free); + + return our_type; +} diff --git a/src/tidy/tidy-actor.h b/src/tidy/tidy-actor.h new file mode 100644 index 000000000..5640ffa83 --- /dev/null +++ b/src/tidy/tidy-actor.h @@ -0,0 +1,88 @@ +/* tidy-actor.h: Base class for Tidy actors + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __TIDY_ACTOR_H__ +#define __TIDY_ACTOR_H__ + +#include +#include + +G_BEGIN_DECLS + +#define TIDY_TYPE_ACTOR (tidy_actor_get_type ()) +#define TIDY_ACTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIDY_TYPE_ACTOR, TidyActor)) +#define TIDY_IS_ACTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TIDY_TYPE_ACTOR)) +#define TIDY_ACTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TIDY_TYPE_ACTOR, TidyActorClass)) +#define TIDY_IS_ACTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TIDY_TYPE_ACTOR)) +#define TIDY_ACTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TIDY_TYPE_ACTOR, TidyActorClass)) + +typedef struct _TidyActor TidyActor; +typedef struct _TidyActorPrivate TidyActorPrivate; +typedef struct _TidyActorClass TidyActorClass; + +/** + * TidyActor: + * + * Base class for stylable actors. The contents of the #TidyActor + * structure are private and should only be accessed through the + * public API. + */ +struct _TidyActor +{ + /*< private >*/ + ClutterActor parent_instance; + + TidyActorPrivate *priv; +}; + +/** + * TidyActorClass: + * + * Base class for stylable actors. + */ +struct _TidyActorClass +{ + /*< private >*/ + ClutterActorClass parent_class; +}; + +GType tidy_actor_get_type (void) G_GNUC_CONST; + +void tidy_actor_set_padding (TidyActor *actor, + const TidyPadding *padding); +void tidy_actor_get_padding (TidyActor *actor, + TidyPadding *padding); + +void tidy_actor_set_alignment (TidyActor *actor, + gdouble x_align, + gdouble y_align); +void tidy_actor_get_alignment (TidyActor *actor, + gdouble *x_align, + gdouble *y_align); +void tidy_actor_set_alignmentx (TidyActor *actor, + ClutterFixed x_align, + ClutterFixed y_align); +void tidy_actor_get_alignmentx (TidyActor *actor, + ClutterFixed *x_align, + ClutterFixed *y_align); + +G_END_DECLS + +#endif /* __TIDY_ACTOR_H__ */ diff --git a/src/tidy/tidy-button.c b/src/tidy/tidy-button.c new file mode 100644 index 000000000..063944628 --- /dev/null +++ b/src/tidy/tidy-button.c @@ -0,0 +1,374 @@ +/* tidy-button.c: Plain button actor + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include + +#include "tidy-button.h" + +#include "tidy-debug.h" +#include "tidy-marshal.h" +#include "tidy-stylable.h" + +enum +{ + PROP_0, + + PROP_LABEL +}; + +enum +{ + CLICKED, + + LAST_SIGNAL +}; + +#define TIDY_BUTTON_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TIDY_TYPE_BUTTON, TidyButtonPrivate)) + +struct _TidyButtonPrivate +{ + gchar *text; + + ClutterTimeline *timeline; + ClutterEffectTemplate *press_tmpl; + + guint8 old_opacity; + + guint is_pressed : 1; +}; + +static guint button_signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE (TidyButton, tidy_button, TIDY_TYPE_FRAME); + +static void +tidy_button_real_pressed (TidyButton *button) +{ + TidyButtonPrivate *priv = button->priv; + ClutterActor *actor = CLUTTER_ACTOR (button); + + if (G_UNLIKELY (!priv->press_tmpl)) + { + priv->timeline = clutter_timeline_new_for_duration (250); + priv->press_tmpl = clutter_effect_template_new (priv->timeline, + clutter_sine_inc_func); + clutter_effect_template_set_timeline_clone (priv->press_tmpl, FALSE); + } + + if (clutter_timeline_is_playing (priv->timeline)) + { + clutter_timeline_stop (priv->timeline); + clutter_actor_set_opacity (actor, priv->old_opacity); + } + + priv->old_opacity = clutter_actor_get_opacity (actor); + + clutter_effect_fade (priv->press_tmpl, actor, + 0x44, + NULL, NULL); +} + +static void +tidy_button_real_released (TidyButton *button) +{ + TidyButtonPrivate *priv = button->priv; + ClutterActor *actor = CLUTTER_ACTOR (button); + + if (G_UNLIKELY (!priv->press_tmpl)) + { + priv->timeline = clutter_timeline_new_for_duration (250); + priv->press_tmpl = clutter_effect_template_new (priv->timeline, + clutter_sine_inc_func); + clutter_effect_template_set_timeline_clone (priv->press_tmpl, FALSE); + } + + if (clutter_timeline_is_playing (priv->timeline)) + clutter_timeline_stop (priv->timeline); + + clutter_effect_fade (priv->press_tmpl, actor, + priv->old_opacity, + NULL, NULL); +} + +static void +tidy_button_construct_child (TidyButton *button) +{ + TidyButtonPrivate *priv = button->priv; + gchar *font_name; + ClutterColor *text_color; + ClutterActor *label; + + if (!priv->text) + return; + + tidy_stylable_get (TIDY_STYLABLE (button), + "font-name", &font_name, + "text-color", &text_color, + NULL); + + label = g_object_new (CLUTTER_TYPE_LABEL, + "font-name", font_name, + "text", priv->text, + "color", text_color, + "alignment", PANGO_ALIGN_CENTER, + "ellipsize", PANGO_ELLIPSIZE_MIDDLE, + "use-markup", TRUE, + "wrap", FALSE, + NULL); + + clutter_actor_show (label); + clutter_container_add_actor (CLUTTER_CONTAINER (button), label); + + clutter_color_free (text_color); + g_free (font_name); +} + +static gboolean +tidy_button_button_press (ClutterActor *actor, + ClutterButtonEvent *event) +{ + if (event->button == 1 && + event->click_count == 1) + { + TidyButton *button = TIDY_BUTTON (actor); + TidyButtonClass *klass = TIDY_BUTTON_GET_CLASS (button); + + button->priv->is_pressed = TRUE; + + clutter_grab_pointer (actor); + + if (klass->pressed) + klass->pressed (button); + + return TRUE; + } + + return FALSE; +} + +static gboolean +tidy_button_button_release (ClutterActor *actor, + ClutterButtonEvent *event) +{ + if (event->button == 1) + { + TidyButton *button = TIDY_BUTTON (actor); + TidyButtonClass *klass = TIDY_BUTTON_GET_CLASS (button); + + if (!button->priv->is_pressed) + return FALSE; + + clutter_ungrab_pointer (); + + button->priv->is_pressed = FALSE; + + if (klass->released) + klass->released (button); + + g_signal_emit (button, button_signals[CLICKED], 0); + + return TRUE; + } + + return FALSE; +} + +static gboolean +tidy_button_leave (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + TidyButton *button = TIDY_BUTTON (actor); + + if (button->priv->is_pressed) + { + TidyButtonClass *klass = TIDY_BUTTON_GET_CLASS (button); + + clutter_ungrab_pointer (); + + button->priv->is_pressed = FALSE; + + if (klass->released) + klass->released (button); + } + + return FALSE; +} + +static void +tidy_button_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TidyButton *button = TIDY_BUTTON (gobject); + + switch (prop_id) + { + case PROP_LABEL: + tidy_button_set_label (button, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +tidy_button_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TidyButtonPrivate *priv = TIDY_BUTTON (gobject)->priv; + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, priv->text); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +tidy_button_finalize (GObject *gobject) +{ + TidyButtonPrivate *priv = TIDY_BUTTON (gobject)->priv; + + g_free (priv->text); + + G_OBJECT_CLASS (tidy_button_parent_class)->finalize (gobject); +} + +static void +tidy_button_dispose (GObject *gobject) +{ + TidyButtonPrivate *priv = TIDY_BUTTON (gobject)->priv; + + if (priv->press_tmpl) + { + g_object_unref (priv->press_tmpl); + g_object_unref (priv->timeline); + + priv->press_tmpl = NULL; + priv->timeline = NULL; + } + + G_OBJECT_CLASS (tidy_button_parent_class)->dispose (gobject); +} + +static void +tidy_button_class_init (TidyButtonClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TidyButtonPrivate)); + + klass->pressed = tidy_button_real_pressed; + klass->released = tidy_button_real_released; + + gobject_class->set_property = tidy_button_set_property; + gobject_class->get_property = tidy_button_get_property; + gobject_class->dispose = tidy_button_dispose; + gobject_class->finalize = tidy_button_finalize; + + actor_class->button_press_event = tidy_button_button_press; + actor_class->button_release_event = tidy_button_button_release; + actor_class->leave_event = tidy_button_leave; + + g_object_class_install_property (gobject_class, + PROP_LABEL, + g_param_spec_string ("label", + "Label", + "Label of the button", + NULL, + G_PARAM_READWRITE)); + + button_signals[CLICKED] = + g_signal_new ("clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TidyButtonClass, clicked), + NULL, NULL, + _tidy_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +tidy_button_init (TidyButton *button) +{ + button->priv = TIDY_BUTTON_GET_PRIVATE (button); +} + +ClutterActor * +tidy_button_new (void) +{ + return g_object_new (TIDY_TYPE_BUTTON, NULL); +} + +ClutterActor * +tidy_button_new_with_label (const gchar *text) +{ + return g_object_new (TIDY_TYPE_BUTTON, "label", text, NULL); +} + +G_CONST_RETURN gchar * +tidy_button_get_label (TidyButton *button) +{ + g_return_val_if_fail (TIDY_IS_BUTTON (button), NULL); + + return button->priv->text; +} + +void +tidy_button_set_label (TidyButton *button, + const gchar *text) +{ + TidyButtonPrivate *priv; + + g_return_if_fail (TIDY_IS_BUTTON (button)); + + priv = button->priv; + + g_free (priv->text); + priv->text = g_strdup (text); + + tidy_button_construct_child (button); + + g_object_notify (G_OBJECT (button), "label"); +} diff --git a/src/tidy/tidy-button.h b/src/tidy/tidy-button.h new file mode 100644 index 000000000..9f0aa44e4 --- /dev/null +++ b/src/tidy/tidy-button.h @@ -0,0 +1,70 @@ +/* tidy-button.h: Plain button actor + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + */ + +#ifndef __TIDY_BUTTON_H__ +#define __TIDY_BUTTON_H__ + +#include + +G_BEGIN_DECLS + +#define TIDY_TYPE_BUTTON (tidy_button_get_type ()) +#define TIDY_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIDY_TYPE_BUTTON, TidyButton)) +#define TIDY_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TIDY_TYPE_BUTTON)) +#define TIDY_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TIDY_TYPE_BUTTON, TidyButtonClass)) +#define TIDY_IS_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TIDY_TYPE_BUTTON)) +#define TIDY_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TIDY_TYPE_BUTTON, TidyButtonClass)) + +typedef struct _TidyButton TidyButton; +typedef struct _TidyButtonPrivate TidyButtonPrivate; +typedef struct _TidyButtonClass TidyButtonClass; + +struct _TidyButton +{ + TidyFrame parent_instance; + + TidyButtonPrivate *priv; +}; + +struct _TidyButtonClass +{ + TidyFrameClass parent_class; + + /* vfuncs, not signals */ + void (* pressed) (TidyButton *button); + void (* released) (TidyButton *button); + + /* signals */ + void (* clicked) (TidyButton *button); +}; + +GType tidy_button_get_type (void) G_GNUC_CONST; + +ClutterActor * tidy_button_new (void); +ClutterActor * tidy_button_new_with_label (const gchar *text); +G_CONST_RETURN gchar *tidy_button_get_label (TidyButton *button); +void tidy_button_set_label (TidyButton *button, + const gchar *text); + +G_END_DECLS + +#endif /* __TIDY_BUTTON_H__ */ diff --git a/src/tidy/tidy-debug.h b/src/tidy/tidy-debug.h new file mode 100644 index 000000000..0efb9821f --- /dev/null +++ b/src/tidy/tidy-debug.h @@ -0,0 +1,4 @@ +#ifndef __TIDY_DEBUG_H__ +#define __TIDY_DEBUG_H__ + +#endif /* __TIDY_DEBUG_H__ */ diff --git a/src/tidy/tidy-enum-types.c.in b/src/tidy/tidy-enum-types.c.in new file mode 100644 index 000000000..5f78912d8 --- /dev/null +++ b/src/tidy/tidy-enum-types.c.in @@ -0,0 +1,30 @@ +/*** BEGIN file-header ***/ +#include "tidy-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("@EnumName@", values); + } + return enum_type_id; +} +/*** END value-tail ***/ diff --git a/src/tidy/tidy-enum-types.h.in b/src/tidy/tidy-enum-types.h.in new file mode 100644 index 000000000..517cccbfa --- /dev/null +++ b/src/tidy/tidy-enum-types.h.in @@ -0,0 +1,25 @@ +/*** BEGIN file-header ***/ +#ifndef __TIDY_ENUM_TYPES_H__ +#define __TIDY_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 /* !__TIDY_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define TIDY_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) + +/*** END value-header ***/ diff --git a/src/tidy/tidy-frame.c b/src/tidy/tidy-frame.c new file mode 100644 index 000000000..b68810efc --- /dev/null +++ b/src/tidy/tidy-frame.c @@ -0,0 +1,515 @@ +/* tidy-frame.c: Simple container with a background + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "tidy-frame.h" +#include "tidy-private.h" +#include "tidy-stylable.h" + +#define TIDY_FRAME_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TIDY_TYPE_FRAME, TidyFramePrivate)) + +enum +{ + PROP_0, + + PROP_CHILD, + PROP_TEXTURE +}; + +struct _TidyFramePrivate +{ + ClutterActor *child; + ClutterActor *texture; +}; + +static ClutterColor default_bg_color = { 0xcc, 0xcc, 0xcc, 0xff }; + +static void clutter_container_iface_init (ClutterContainerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (TidyFrame, tidy_frame, TIDY_TYPE_ACTOR, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, + clutter_container_iface_init)); + +static void +tidy_frame_get_preferred_width (ClutterActor *actor, + ClutterUnit for_height, + ClutterUnit *min_width_p, + ClutterUnit *natural_width_p) +{ + TidyFramePrivate *priv = TIDY_FRAME (actor)->priv; + TidyPadding padding = { 0, }; + ClutterUnit min_width, natural_width; + + tidy_actor_get_padding (TIDY_ACTOR (actor), &padding); + + min_width = 0; + natural_width = padding.left + padding.right; + + if (priv->child) + { + ClutterUnit child_min, child_natural; + + clutter_actor_get_preferred_width (priv->child, for_height, + &child_min, + &child_natural); + + min_width += child_min; + natural_width += child_natural; + } + + if (min_width_p) + *min_width_p = min_width; + + if (natural_width_p) + *natural_width_p = natural_width; +} + +static void +tidy_frame_get_preferred_height (ClutterActor *actor, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p) +{ + TidyFramePrivate *priv = TIDY_FRAME (actor)->priv; + TidyPadding padding = { 0, }; + ClutterUnit min_height, natural_height; + + tidy_actor_get_padding (TIDY_ACTOR (actor), &padding); + + min_height = 0; + natural_height = padding.top + padding.bottom; + + if (priv->child) + { + ClutterUnit child_min, child_natural; + + clutter_actor_get_preferred_height (priv->child, for_width, + &child_min, + &child_natural); + + min_height += child_min; + natural_height += child_natural; + } + + if (min_height_p) + *min_height_p = min_height; + + if (natural_height_p) + *natural_height_p = natural_height; +} + +static void +tidy_frame_allocate (ClutterActor *actor, + const ClutterActorBox *box, + gboolean origin_changed) +{ + TidyFramePrivate *priv = TIDY_FRAME (actor)->priv; + ClutterActorClass *klass; + + klass = CLUTTER_ACTOR_CLASS (tidy_frame_parent_class); + klass->allocate (actor, box, origin_changed); + + if (priv->texture) + { + ClutterActorBox texture_box = { 0, }; + + texture_box.x1 = 0; + texture_box.y1 = 0; + texture_box.x2 = box->x2 - box->x1; + texture_box.y2 = box->y2 - box->y1; + + clutter_actor_allocate (priv->texture, &texture_box, origin_changed); + } + + if (priv->child) + { + TidyPadding padding = { 0, }; + ClutterFixed x_align, y_align; + ClutterUnit available_width, available_height; + ClutterUnit child_width, child_height; + ClutterActorBox child_box = { 0, }; + + tidy_actor_get_padding (TIDY_ACTOR (actor), &padding); + tidy_actor_get_alignmentx (TIDY_ACTOR (actor), &x_align, &y_align); + + 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; + + clutter_actor_get_preferred_size (priv->child, + NULL, NULL, + &child_width, + &child_height); + + if (child_width > available_width) + child_width = available_width; + + if (child_height > available_height) + child_height = available_height; + + child_box.x1 = CLUTTER_FIXED_MUL ((available_width - child_width), + x_align) + + padding.left; + child_box.y1 = CLUTTER_FIXED_MUL ((available_height - child_height), + y_align) + + padding.top; + + child_box.x2 = child_box.x1 + child_width; + child_box.y2 = child_box.y1 + child_height; + + clutter_actor_allocate (priv->child, &child_box, origin_changed); + } +} + +static void +tidy_frame_paint (ClutterActor *actor) +{ + TidyFrame *frame = TIDY_FRAME (actor); + TidyFramePrivate *priv = frame->priv; + + cogl_push_matrix (); + + if (priv->texture) + clutter_actor_paint (priv->texture); + else + { + ClutterActorBox allocation = { 0, }; + ClutterColor *bg_color; + guint w, h; + + tidy_stylable_get (TIDY_STYLABLE (frame), "bg-color", &bg_color, NULL); + if (!bg_color) + bg_color = &default_bg_color; + + bg_color->alpha = clutter_actor_get_paint_opacity (actor) + * bg_color->alpha + / 255; + + clutter_actor_get_allocation_box (actor, &allocation); + + w = CLUTTER_UNITS_TO_DEVICE (allocation.x2 - allocation.x1); + h = CLUTTER_UNITS_TO_DEVICE (allocation.y2 - allocation.y1); + + cogl_color (bg_color); + cogl_rectangle (0, 0, w, h); + + if (bg_color != &default_bg_color) + clutter_color_free (bg_color); + } + + if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child)) + clutter_actor_paint (priv->child); + + cogl_pop_matrix (); +} + +static void +tidy_frame_pick (ClutterActor *actor, + const ClutterColor *pick_color) +{ + TidyFramePrivate *priv = TIDY_FRAME (actor)->priv; + + /* chain up, so we get a box with our coordinates */ + CLUTTER_ACTOR_CLASS (tidy_frame_parent_class)->pick (actor, pick_color); + + if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child)) + clutter_actor_paint (priv->child); +} + +static void +tidy_frame_dispose (GObject *gobject) +{ + TidyFramePrivate *priv = TIDY_FRAME (gobject)->priv; + + if (priv->child) + { + clutter_actor_unparent (priv->child); + priv->child = NULL; + } + + if (priv->texture) + { + clutter_actor_unparent (priv->texture); + priv->texture = NULL; + } + + G_OBJECT_CLASS (tidy_frame_parent_class)->dispose (gobject); +} + +static void +tidy_frame_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_CHILD: + clutter_container_add_actor (CLUTTER_CONTAINER (gobject), + g_value_get_object (value)); + break; + + case PROP_TEXTURE: + tidy_frame_set_texture (TIDY_FRAME (gobject), + g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +tidy_frame_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TidyFramePrivate *priv = TIDY_FRAME (gobject)->priv; + + switch (prop_id) + { + case PROP_CHILD: + g_value_set_object (value, priv->child); + break; + + case PROP_TEXTURE: + g_value_set_object (value, priv->texture); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +tidy_frame_class_init (TidyFrameClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TidyFramePrivate)); + + gobject_class->set_property = tidy_frame_set_property; + gobject_class->get_property = tidy_frame_get_property; + gobject_class->dispose = tidy_frame_dispose; + + actor_class->pick = tidy_frame_pick; + actor_class->paint = tidy_frame_paint; + actor_class->allocate = tidy_frame_allocate; + actor_class->get_preferred_width = tidy_frame_get_preferred_width; + actor_class->get_preferred_height = tidy_frame_get_preferred_height; + + g_object_class_install_property (gobject_class, + PROP_CHILD, + g_param_spec_object ("child", + "Child", + "The child of the frame", + CLUTTER_TYPE_ACTOR, + TIDY_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_TEXTURE, + g_param_spec_object ("texture", + "Texture", + "The background texture of the frame", + CLUTTER_TYPE_ACTOR, + TIDY_PARAM_READWRITE)); +} + +static void +tidy_frame_init (TidyFrame *frame) +{ + frame->priv = TIDY_FRAME_GET_PRIVATE (frame); +} + +static void +tidy_frame_add_actor (ClutterContainer *container, + ClutterActor *actor) +{ + TidyFramePrivate *priv = TIDY_FRAME (container)->priv; + + if (priv->child) + clutter_actor_unparent (priv->child); + + clutter_actor_set_parent (actor, CLUTTER_ACTOR (container)); + priv->child = actor; + + clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); + + g_signal_emit_by_name (container, "actor-added", actor); + + g_object_notify (G_OBJECT (container), "child"); +} + +static void +tidy_frame_remove_actor (ClutterContainer *container, + ClutterActor *actor) +{ + TidyFramePrivate *priv = TIDY_FRAME (container)->priv; + + if (priv->child == actor) + { + g_object_ref (priv->child); + + clutter_actor_unparent (priv->child); + priv->child = NULL; + + clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); + + g_signal_emit_by_name (container, "actor-removed", priv->child); + + g_object_unref (priv->child); + } +} + +static void +tidy_frame_foreach (ClutterContainer *container, + ClutterCallback callback, + gpointer callback_data) +{ + TidyFramePrivate *priv = TIDY_FRAME (container)->priv; + + if (priv->texture) + callback (priv->texture, callback_data); + + if (priv->child) + callback (priv->child, callback_data); +} + +static void +tidy_frame_lower (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling) +{ + /* single child */ +} + +static void +tidy_frame_raise (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling) +{ + /* single child */ +} + +static void +tidy_frame_sort_depth_order (ClutterContainer *container) +{ + /* single child */ +} + +static void +clutter_container_iface_init (ClutterContainerIface *iface) +{ + iface->add = tidy_frame_add_actor; + iface->remove = tidy_frame_remove_actor; + iface->foreach = tidy_frame_foreach; + iface->lower = tidy_frame_lower; + iface->raise = tidy_frame_raise; + iface->sort_depth_order = tidy_frame_sort_depth_order; +} + +ClutterActor * +tidy_frame_new (void) +{ + return g_object_new (TIDY_TYPE_FRAME, NULL); +} + +ClutterActor * +tidy_frame_get_child (TidyFrame *frame) +{ + g_return_val_if_fail (TIDY_IS_FRAME (frame), NULL); + + return frame->priv->child; +} + +void +tidy_frame_set_texture (TidyFrame *frame, + ClutterActor *texture) +{ + TidyFramePrivate *priv; + + g_return_if_fail (TIDY_IS_FRAME (frame)); + g_return_if_fail (CLUTTER_IS_ACTOR (texture)); + + priv = frame->priv; + + if (priv->texture == texture) + return; + + if (priv->texture) + { + clutter_actor_unparent (priv->texture); + priv->texture = NULL; + } + + if (texture) + { + ClutterActor *parent = clutter_actor_get_parent (texture); + + if (G_UNLIKELY (parent != NULL)) + { + g_warning ("Unable to set the background texture of type `%s' for " + "the frame of type `%s': the texture actor is already " + "a child of a container of type `%s'", + g_type_name (G_OBJECT_TYPE (texture)), + g_type_name (G_OBJECT_TYPE (frame)), + g_type_name (G_OBJECT_TYPE (parent))); + return; + } + + priv->texture = texture; + clutter_actor_set_parent (texture, CLUTTER_ACTOR (frame)); + } + + clutter_actor_queue_relayout (CLUTTER_ACTOR (frame)); + + g_object_notify (G_OBJECT (frame), "texture"); +} + +ClutterActor * +tidy_frame_get_texture (TidyFrame *frame) +{ + g_return_val_if_fail (TIDY_IS_FRAME (frame), NULL); + + return frame->priv->texture; +} + diff --git a/src/tidy/tidy-frame.h b/src/tidy/tidy-frame.h new file mode 100644 index 000000000..7aa4e529c --- /dev/null +++ b/src/tidy/tidy-frame.h @@ -0,0 +1,64 @@ +/* tidy-frame.h: Simple container with a background + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Written by: Emmanuele Bassi + */ + +#ifndef __TIDY_FRAME_H__ +#define __TIDY_FRAME_H__ + +#include +#include + +G_BEGIN_DECLS + +#define TIDY_TYPE_FRAME (tidy_frame_get_type ()) +#define TIDY_FRAME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIDY_TYPE_FRAME, TidyFrame)) +#define TIDY_IS_FRAME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TIDY_TYPE_FRAME)) +#define TIDY_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TIDY_TYPE_FRAME, TidyFrameClass)) +#define TIDY_IS_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TIDY_TYPE_FRAME)) +#define TIDY_FRAME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TIDY_TYPE_FRAME, TidyFrameClass)) + +typedef struct _TidyFrame TidyFrame; +typedef struct _TidyFramePrivate TidyFramePrivate; +typedef struct _TidyFrameClass TidyFrameClass; + +struct _TidyFrame +{ + TidyActor parent_instance; + + TidyFramePrivate *priv; +}; + +struct _TidyFrameClass +{ + TidyActorClass parent_class; +}; + +GType tidy_frame_get_type (void) G_GNUC_CONST; + +ClutterActor *tidy_frame_new (void); +ClutterActor *tidy_frame_get_child (TidyFrame *frame); +void tidy_frame_set_texture (TidyFrame *frame, + ClutterActor *actor); +ClutterActor *tidy_frame_get_texture (TidyFrame *frame); + +G_END_DECLS + +#endif /* __TIDY_FRAME_H__ */ diff --git a/src/tidy/tidy-grid.c b/src/tidy/tidy-grid.c new file mode 100644 index 000000000..402013b1d --- /dev/null +++ b/src/tidy/tidy-grid.c @@ -0,0 +1,1005 @@ +/* tidy-grid.h: Reflowing grid layout container for clutter. + * + * Copyright (C) 2008 Intel Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Written by: Øyvind Kolås + */ + +/* TODO: + * + * - Better names for properties. + * - Caching layouted positions? (perhaps needed for huge collections) + * - More comments / overall concept on how the layouting is done. + * - Allow more layout directions than just row major / column major. + */ + +#include +#include +#include + +#include "tidy-grid.h" + +typedef struct _TidyGridActorData TidyGridActorData; + +static void tidy_grid_dispose (GObject *object); +static void tidy_grid_finalize (GObject *object); + +static void tidy_grid_finalize (GObject *object); + +static void tidy_grid_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void tidy_grid_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void clutter_container_iface_init (ClutterContainerIface *iface); + +static void tidy_grid_real_add (ClutterContainer *container, + ClutterActor *actor); +static void tidy_grid_real_remove (ClutterContainer *container, + ClutterActor *actor); +static void tidy_grid_real_foreach (ClutterContainer *container, + ClutterCallback callback, + gpointer user_data); +static void tidy_grid_real_raise (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling); +static void tidy_grid_real_lower (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling); +static void +tidy_grid_real_sort_depth_order (ClutterContainer *container); + +static void +tidy_grid_free_actor_data (gpointer data); + +static void tidy_grid_paint (ClutterActor *actor); + +static void tidy_grid_pick (ClutterActor *actor, + const ClutterColor *color); + +static void +tidy_grid_get_preferred_width (ClutterActor *self, + ClutterUnit for_height, + ClutterUnit *min_width_p, + ClutterUnit *natural_width_p); + +static void +tidy_grid_get_preferred_height (ClutterActor *self, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p); + +static void tidy_grid_allocate (ClutterActor *self, + const ClutterActorBox *box, + gboolean absolute_origin_changed); + +G_DEFINE_TYPE_WITH_CODE (TidyGrid, tidy_grid, + CLUTTER_TYPE_ACTOR, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, + clutter_container_iface_init)); + +#define TIDY_GRID_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TIDY_TYPE_GRID, \ + TidyGridPrivate)) + +struct _TidyGridPrivate +{ + ClutterUnit for_height, for_width; + ClutterUnit pref_width, pref_height; + ClutterUnit alloc_width, alloc_height; + + gboolean absolute_origin_changed; + GHashTable *hash_table; + GList *list; + + gboolean homogenous_rows; + gboolean homogenous_columns; + gboolean end_align; + ClutterUnit column_gap, row_gap; + gdouble valign, halign; + + gboolean column_major; + + gboolean first_of_batch; + ClutterUnit a_current_sum, a_wrap; + ClutterUnit max_extent_a; + ClutterUnit max_extent_b; +}; + +enum +{ + PROP_0, + PROP_HOMOGENOUS_ROWS, + PROP_HOMOGENOUS_COLUMNS, + PROP_ROW_GAP, + PROP_COLUMN_GAP, + PROP_VALIGN, + PROP_HALIGN, + PROP_END_ALIGN, + PROP_COLUMN_MAJOR, +}; + +struct _TidyGridActorData +{ + gboolean xpos_set, ypos_set; + ClutterUnit xpos, ypos; + ClutterUnit pref_width, pref_height; +}; + +static void +tidy_grid_class_init (TidyGridClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + ClutterActorClass *actor_class = (ClutterActorClass *) klass; + + gobject_class->dispose = tidy_grid_dispose; + gobject_class->finalize = tidy_grid_finalize; + + gobject_class->set_property = tidy_grid_set_property; + gobject_class->get_property = tidy_grid_get_property; + + actor_class->paint = tidy_grid_paint; + actor_class->pick = tidy_grid_pick; + actor_class->get_preferred_width = tidy_grid_get_preferred_width; + actor_class->get_preferred_height = tidy_grid_get_preferred_height; + actor_class->allocate = tidy_grid_allocate; + + g_type_class_add_private (klass, sizeof (TidyGridPrivate)); + + + g_object_class_install_property + (gobject_class, + PROP_ROW_GAP, + clutter_param_spec_unit ("row-gap", + "Row gap", + "gap between rows in the layout", + 0, CLUTTER_MAXUNIT, + 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_COLUMN_GAP, + clutter_param_spec_unit ("column-gap", + "Column gap", + "gap between columns in the layout", + 0, CLUTTER_MAXUNIT, + 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + + g_object_class_install_property + (gobject_class, + PROP_HOMOGENOUS_ROWS, + g_param_spec_boolean ("homogenous-rows", + "homogenous rows", + "Should all rows have the same height?", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_HOMOGENOUS_COLUMNS, + g_param_spec_boolean ("homogenous-columns", + "homogenous columns", + "Should all columns have the same height?", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_COLUMN_MAJOR, + g_param_spec_boolean ("column-major", + "column-major", + "Do a column filling first instead of row filling first", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_END_ALIGN, + g_param_spec_boolean ("end-align", + "end-align", + "Right/bottom aligned rows/columns", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_VALIGN, + g_param_spec_double ("valign", + "Vertical align", + "Vertical alignment of items within cells", + 0.0, 1.0, 0.0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (gobject_class, + PROP_HALIGN, + g_param_spec_double ("halign", + "Horizontal align", + "Horizontal alignment of items within cells", + 0.0, 1.0, 0.0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + +} + +static void +clutter_container_iface_init (ClutterContainerIface *iface) +{ + iface->add = tidy_grid_real_add; + iface->remove = tidy_grid_real_remove; + iface->foreach = tidy_grid_real_foreach; + iface->raise = tidy_grid_real_raise; + iface->lower = tidy_grid_real_lower; + iface->sort_depth_order = tidy_grid_real_sort_depth_order; +} + +static void +tidy_grid_init (TidyGrid *self) +{ + TidyGridPrivate *priv; + + self->priv = priv = TIDY_GRID_GET_PRIVATE (self); + + /* do not unref in the hashtable, the reference is for now kept by the list + * (double bookkeeping sucks) + */ + priv->hash_table + = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + tidy_grid_free_actor_data); +} + +static void +tidy_grid_dispose (GObject *object) +{ + TidyGrid *self = (TidyGrid *) object; + TidyGridPrivate *priv; + + priv = self->priv; + + /* Destroy all of the children. This will cause them to be removed + from the container and unparented */ + clutter_container_foreach (CLUTTER_CONTAINER (object), + (ClutterCallback) clutter_actor_destroy, + NULL); + + G_OBJECT_CLASS (tidy_grid_parent_class)->dispose (object); +} + +static void +tidy_grid_finalize (GObject *object) +{ + TidyGrid *self = (TidyGrid *) object; + TidyGridPrivate *priv = self->priv; + + g_hash_table_destroy (priv->hash_table); + + G_OBJECT_CLASS (tidy_grid_parent_class)->finalize (object); +} + + +void +tidy_grid_set_end_align (TidyGrid *self, + gboolean value) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->end_align = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +gboolean +tidy_grid_get_end_align (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->end_align; +} + +void +tidy_grid_set_homogenous_rows (TidyGrid *self, + gboolean value) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->homogenous_rows = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +gboolean +tidy_grid_get_homogenous_rows (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->homogenous_rows; +} + + +void +tidy_grid_set_homogenous_columns (TidyGrid *self, + gboolean value) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->homogenous_columns = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + + +gboolean +tidy_grid_get_homogenous_columns (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->homogenous_columns; +} + + +void +tidy_grid_set_column_major (TidyGrid *self, + gboolean value) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->column_major = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +gboolean +tidy_grid_get_column_major (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->column_major; +} + +void +tidy_grid_set_column_gap (TidyGrid *self, + ClutterUnit value) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->column_gap = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +ClutterUnit +tidy_grid_get_column_gap (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->column_gap; +} + + + +void +tidy_grid_set_row_gap (TidyGrid *self, + ClutterUnit value) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->row_gap = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +ClutterUnit +tidy_grid_get_row_gap (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->row_gap; +} + + +void +tidy_grid_set_valign (TidyGrid *self, + gdouble value) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->valign = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +gdouble +tidy_grid_get_valign (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->valign; +} + + + +void +tidy_grid_set_halign (TidyGrid *self, + gdouble value) + +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + priv->halign = value; + clutter_actor_queue_relayout (CLUTTER_ACTOR (self)); +} + +gdouble +tidy_grid_get_halign (TidyGrid *self) +{ + TidyGridPrivate *priv = TIDY_GRID_GET_PRIVATE (self); + return priv->halign; +} + + +static void +tidy_grid_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TidyGrid *grid = TIDY_GRID (object); + + TidyGridPrivate *priv; + + priv = TIDY_GRID_GET_PRIVATE (object); + + switch (prop_id) + { + case PROP_END_ALIGN: + tidy_grid_set_end_align (grid, g_value_get_boolean (value)); + break; + case PROP_HOMOGENOUS_ROWS: + tidy_grid_set_homogenous_rows (grid, g_value_get_boolean (value)); + break; + case PROP_HOMOGENOUS_COLUMNS: + tidy_grid_set_homogenous_columns (grid, g_value_get_boolean (value)); + break; + case PROP_COLUMN_MAJOR: + tidy_grid_set_column_major (grid, g_value_get_boolean (value)); + break; + case PROP_COLUMN_GAP: + tidy_grid_set_column_gap (grid, clutter_value_get_unit (value)); + break; + case PROP_ROW_GAP: + tidy_grid_set_row_gap (grid, clutter_value_get_unit (value)); + break; + case PROP_VALIGN: + tidy_grid_set_valign (grid, g_value_get_double (value)); + break; + case PROP_HALIGN: + tidy_grid_set_halign (grid, g_value_get_double (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +tidy_grid_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TidyGrid *grid = TIDY_GRID (object); + + TidyGridPrivate *priv; + + priv = TIDY_GRID_GET_PRIVATE (object); + + switch (prop_id) + { + case PROP_HOMOGENOUS_ROWS: + g_value_set_boolean (value, tidy_grid_get_homogenous_rows (grid)); + break; + case PROP_HOMOGENOUS_COLUMNS: + g_value_set_boolean (value, tidy_grid_get_homogenous_columns (grid)); + break; + case PROP_END_ALIGN: + g_value_set_boolean (value, tidy_grid_get_end_align (grid)); + break; + case PROP_COLUMN_MAJOR: + g_value_set_boolean (value, tidy_grid_get_column_major (grid)); + break; + case PROP_COLUMN_GAP: + clutter_value_set_unit (value, tidy_grid_get_column_gap (grid)); + break; + case PROP_ROW_GAP: + clutter_value_set_unit (value, tidy_grid_get_row_gap (grid)); + break; + case PROP_VALIGN: + g_value_set_double (value, tidy_grid_get_valign (grid)); + break; + case PROP_HALIGN: + g_value_set_double (value, tidy_grid_get_halign (grid)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +tidy_grid_free_actor_data (gpointer data) +{ + g_slice_free (TidyGridActorData, data); +} + +ClutterActor * +tidy_grid_new (void) +{ + ClutterActor *self = g_object_new (TIDY_TYPE_GRID, NULL); + + return self; +} + +static void +tidy_grid_real_add (ClutterContainer *container, + ClutterActor *actor) +{ + TidyGridPrivate *priv; + TidyGridActorData *data; + + g_return_if_fail (TIDY_IS_GRID (container)); + + priv = TIDY_GRID (container)->priv; + + g_object_ref (actor); + + clutter_actor_set_parent (actor, CLUTTER_ACTOR (container)); + + data = g_slice_alloc0 (sizeof (TidyGridActorData)); + + priv->list = g_list_append (priv->list, actor); + g_hash_table_insert (priv->hash_table, actor, data); + + g_signal_emit_by_name (container, "actor-added", actor); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); + + g_object_unref (actor); +} + +static void +tidy_grid_real_remove (ClutterContainer *container, + ClutterActor *actor) +{ + TidyGrid *layout = TIDY_GRID (container); + TidyGridPrivate *priv = layout->priv; + + g_object_ref (actor); + + if (g_hash_table_remove (priv->hash_table, actor)) + { + clutter_actor_unparent (actor); + + clutter_actor_queue_relayout (CLUTTER_ACTOR (layout)); + + g_signal_emit_by_name (container, "actor-removed", actor); + + if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (layout))) + clutter_actor_queue_redraw (CLUTTER_ACTOR (layout)); + } + priv->list = g_list_remove (priv->list, actor); + + g_object_unref (actor); +} + +static void +tidy_grid_real_foreach (ClutterContainer *container, + ClutterCallback callback, + gpointer user_data) +{ + TidyGrid *layout = TIDY_GRID (container); + TidyGridPrivate *priv = layout->priv; + + g_list_foreach (priv->list, (GFunc) callback, user_data); +} + +static void +tidy_grid_real_raise (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling) +{ + /* STUB */ +} + +static void +tidy_grid_real_lower (ClutterContainer *container, + ClutterActor *actor, + ClutterActor *sibling) +{ + /* STUB */ +} + +static void +tidy_grid_real_sort_depth_order (ClutterContainer *container) +{ + /* STUB */ +} + +static void +tidy_grid_paint (ClutterActor *actor) +{ + TidyGrid *layout = (TidyGrid *) actor; + TidyGridPrivate *priv = layout->priv; + GList *child_item; + + for (child_item = priv->list; + child_item != NULL; + child_item = child_item->next) + { + ClutterActor *child = child_item->data; + + g_assert (child != NULL); + + if (CLUTTER_ACTOR_IS_VISIBLE (child)) + clutter_actor_paint (child); + } + +} + +static void +tidy_grid_pick (ClutterActor *actor, + const ClutterColor *color) +{ + /* Chain up so we get a bounding box pained (if we are reactive) */ + CLUTTER_ACTOR_CLASS (tidy_grid_parent_class)->pick (actor, color); + + /* Just forward to the paint call which in turn will trigger + * the child actors also getting 'picked'. + */ + if (CLUTTER_ACTOR_IS_VISIBLE (actor)) + tidy_grid_paint (actor); +} + +static void +tidy_grid_get_preferred_width (ClutterActor *self, + ClutterUnit for_height, + ClutterUnit *min_width_p, + ClutterUnit *natural_width_p) +{ + TidyGrid *layout = (TidyGrid *) self; + TidyGridPrivate *priv = layout->priv; + ClutterUnit natural_width; + + natural_width = CLUTTER_UNITS_FROM_INT (200); + if (min_width_p) + *min_width_p = natural_width; + if (natural_width_p) + *natural_width_p = natural_width; + + priv->pref_width = natural_width; +} + +static void +tidy_grid_get_preferred_height (ClutterActor *self, + ClutterUnit for_width, + ClutterUnit *min_height_p, + ClutterUnit *natural_height_p) +{ + TidyGrid *layout = (TidyGrid *) self; + TidyGridPrivate *priv = layout->priv; + ClutterUnit natural_height; + + natural_height = CLUTTER_UNITS_FROM_INT (200); + + priv->for_width = for_width; + priv->pref_height = natural_height; + + if (min_height_p) + *min_height_p = natural_height; + if (natural_height_p) + *natural_height_p = natural_height; +} + +static ClutterUnit +compute_row_height (GList *siblings, + ClutterUnit best_yet, + ClutterUnit current_a, + TidyGridPrivate *priv) +{ + GList *l; + + gboolean homogenous_a; + gboolean homogenous_b; + ClutterUnit gap; + + if (priv->column_major) + { + homogenous_b = priv->homogenous_columns; + homogenous_a = priv->homogenous_rows; + gap = priv->row_gap; + } + else + { + homogenous_a = priv->homogenous_columns; + homogenous_b = priv->homogenous_rows; + gap = priv->column_gap; + } + + for (l = siblings; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + ClutterUnit natural_width, natural_height; + + /* each child will get as much space as they require */ + clutter_actor_get_preferred_size (CLUTTER_ACTOR (child), + NULL, NULL, + &natural_width, &natural_height); + + if (priv->column_major) + { + ClutterUnit temp = natural_height; + natural_height = natural_width; + natural_width = temp; + } + + /* if the primary axis is homogenous, each additional item is the same + * width */ + if (homogenous_a) + natural_width = priv->max_extent_a; + + if (natural_height > best_yet) + best_yet = natural_height; + + /* if the child is overflowing, we wrap to next line */ + if (current_a + natural_width + gap > priv->a_wrap) + { + return best_yet; + } + current_a += natural_width + gap; + } + return best_yet; +} + + + + +static ClutterUnit +compute_row_start (GList *siblings, + ClutterUnit start_x, + TidyGridPrivate *priv) +{ + ClutterUnit current_a = start_x; + GList *l; + + gboolean homogenous_a; + gboolean homogenous_b; + ClutterUnit gap; + + if (priv->column_major) + { + homogenous_b = priv->homogenous_columns; + homogenous_a = priv->homogenous_rows; + gap = priv->row_gap; + } + else + { + homogenous_a = priv->homogenous_columns; + homogenous_b = priv->homogenous_rows; + gap = priv->column_gap; + } + + for (l = siblings; l != NULL; l = l->next) + { + ClutterActor *child = l->data; + ClutterUnit natural_width, natural_height; + + /* each child will get as much space as they require */ + clutter_actor_get_preferred_size (CLUTTER_ACTOR (child), + NULL, NULL, + &natural_width, &natural_height); + + + if (priv->column_major) + natural_width = natural_height; + + /* if the primary axis is homogenous, each additional item is the same width */ + if (homogenous_a) + natural_width = priv->max_extent_a; + + /* if the child is overflowing, we wrap to next line */ + if (current_a + natural_width + gap > priv->a_wrap) + { + if (current_a == start_x) + return start_x; + return (priv->a_wrap - current_a); + } + current_a += natural_width + gap; + } + return (priv->a_wrap - current_a); +} + +static void +tidy_grid_allocate (ClutterActor *self, + const ClutterActorBox *box, + gboolean absolute_origin_changed) +{ + TidyGrid *layout = (TidyGrid *) self; + TidyGridPrivate *priv = layout->priv; + + ClutterUnit current_a; + ClutterUnit current_b; + ClutterUnit next_b; + ClutterUnit agap; + ClutterUnit bgap; + + gboolean homogenous_a; + gboolean homogenous_b; + gdouble aalign; + gdouble balign; + + current_a = current_b = next_b = 0; + + GList *iter; + + /* chain up to set actor->allocation */ + CLUTTER_ACTOR_CLASS (tidy_grid_parent_class) + ->allocate (self, box, absolute_origin_changed); + + priv->alloc_width = box->x2 - box->x1; + priv->alloc_height = box->y2 - box->y1; + priv->absolute_origin_changed = absolute_origin_changed; + + /* Make sure we have calculated the preferred size */ + /* what does this do? */ + clutter_actor_get_preferred_size (self, NULL, NULL, NULL, NULL); + + + if (priv->column_major) + { + priv->a_wrap = priv->alloc_height; + homogenous_b = priv->homogenous_columns; + homogenous_a = priv->homogenous_rows; + aalign = priv->valign; + balign = priv->halign; + agap = priv->row_gap; + bgap = priv->column_gap; + } + else + { + priv->a_wrap = priv->alloc_width; + homogenous_a = priv->homogenous_columns; + homogenous_b = priv->homogenous_rows; + aalign = priv->halign; + balign = priv->valign; + agap = priv->column_gap; + bgap = priv->row_gap; + } + + priv->max_extent_a = 0; + priv->max_extent_b = 0; + + priv->first_of_batch = TRUE; + + if (homogenous_a || + homogenous_b) + { + for (iter = priv->list; iter; iter = iter->next) + { + ClutterActor *child = iter->data; + ClutterUnit natural_width; + ClutterUnit natural_height; + + /* each child will get as much space as they require */ + clutter_actor_get_preferred_size (CLUTTER_ACTOR (child), + NULL, NULL, + &natural_width, &natural_height); + if (natural_width > priv->max_extent_a) + priv->max_extent_a = natural_width; + if (natural_height > priv->max_extent_b) + priv->max_extent_b = natural_width; + } + } + + if (priv->column_major) + { + ClutterUnit temp = priv->max_extent_a; + priv->max_extent_a = priv->max_extent_b; + priv->max_extent_b = temp; + } + + for (iter = priv->list; iter; iter=iter->next) + { + ClutterActor *child = iter->data; + ClutterUnit natural_a; + ClutterUnit natural_b; + + /* each child will get as much space as they require */ + clutter_actor_get_preferred_size (CLUTTER_ACTOR (child), + NULL, NULL, + &natural_a, &natural_b); + + if (priv->column_major) /* swap axes around if column is major */ + { + ClutterUnit temp = natural_a; + natural_a = natural_b; + natural_b = temp; + } + + /* if the child is overflowing, we wrap to next line */ + if (current_a + natural_a > priv->a_wrap || + (homogenous_a && current_a + priv->max_extent_a > priv->a_wrap)) + { + current_b = next_b + bgap; + current_a = 0; + next_b = current_b + bgap; + priv->first_of_batch = TRUE; + } + + if (priv->end_align && + priv->first_of_batch) + { + current_a = compute_row_start (iter, current_a, priv); + priv->first_of_batch = FALSE; + } + + if (next_b-current_b < natural_b) + next_b = current_b + natural_b; + + { + ClutterUnit row_height; + ClutterActorBox child_box; + + if (homogenous_b) + { + row_height = priv->max_extent_b; + } + else + { + row_height = compute_row_height (iter, next_b-current_b, + current_a, priv); + } + + if (homogenous_a) + { + child_box.x1 = current_a + (priv->max_extent_a-natural_a) * aalign; + child_box.x2 = child_box.x1 + natural_a; + + } + else + { + child_box.x1 = current_a; + child_box.x2 = child_box.x1 + natural_a; + } + + child_box.y1 = current_b + (row_height-natural_b) * balign; + child_box.y2 = child_box.y1 + natural_b; + + + if (priv->column_major) + { + ClutterUnit temp = child_box.x1; + child_box.x1 = child_box.y1; + child_box.y1 = temp; + + temp = child_box.x2; + child_box.x2 = child_box.y2; + child_box.y2 = temp; + } + + /* update the allocation */ + clutter_actor_allocate (CLUTTER_ACTOR (child), + &child_box, + absolute_origin_changed); + + if (homogenous_a) + { + current_a += priv->max_extent_a + agap; + } + else + { + current_a += natural_a + agap; + } + } + } +} diff --git a/src/tidy/tidy-grid.h b/src/tidy/tidy-grid.h new file mode 100644 index 000000000..374baeba2 --- /dev/null +++ b/src/tidy/tidy-grid.h @@ -0,0 +1,99 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2008 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __TIDY_GRID_H__ +#define __TIDY_GRID_H__ + +#include + +G_BEGIN_DECLS + +#define TIDY_TYPE_GRID (tidy_grid_get_type()) +#define TIDY_GRID(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TIDY_TYPE_GRID, \ + TidyGrid)) +#define TIDY_GRID_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TIDY_TYPE_GRID, \ + TidyGridClass)) +#define TIDY_IS_GRID(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + TIDY_TYPE_GRID)) +#define TIDY_IS_GRID_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + TIDY_TYPE_GRID)) +#define TIDY_GRID_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TIDY_TYPE_GRID, \ + TidyGridClass)) + +typedef struct _TidyGrid TidyGrid; +typedef struct _TidyGridClass TidyGridClass; +typedef struct _TidyGridPrivate TidyGridPrivate; + +struct _TidyGridClass +{ + ClutterActorClass parent_class; +}; + +struct _TidyGrid +{ + ClutterActor parent; + + TidyGridPrivate *priv; +}; + +GType tidy_grid_get_type (void) G_GNUC_CONST; + +ClutterActor *tidy_grid_new (void); +void tidy_grid_set_end_align (TidyGrid *self, + gboolean value); +gboolean tidy_grid_get_end_align (TidyGrid *self); +void tidy_grid_set_homogenous_rows (TidyGrid *self, + gboolean value); +gboolean tidy_grid_get_homogenous_rows (TidyGrid *self); +void tidy_grid_set_homogenous_columns (TidyGrid *self, + gboolean value); +gboolean tidy_grid_get_homogenous_columns (TidyGrid *self); +void tidy_grid_set_column_major (TidyGrid *self, + gboolean value); +gboolean tidy_grid_get_column_major (TidyGrid *self); +void tidy_grid_set_row_gap (TidyGrid *self, + ClutterUnit value); +ClutterUnit tidy_grid_get_row_gap (TidyGrid *self); +void tidy_grid_set_column_gap (TidyGrid *self, + ClutterUnit value); +ClutterUnit tidy_grid_get_column_gap (TidyGrid *self); +void tidy_grid_set_valign (TidyGrid *self, + gdouble value); +gdouble tidy_grid_get_valign (TidyGrid *self); +void tidy_grid_set_halign (TidyGrid *self, + gdouble value); +gdouble tidy_grid_get_halign (TidyGrid *self); + +G_END_DECLS + +#endif /* __TIDY_GRID_H__ */ diff --git a/src/tidy/tidy-marshal.list b/src/tidy/tidy-marshal.list new file mode 100644 index 000000000..4d905094a --- /dev/null +++ b/src/tidy/tidy-marshal.list @@ -0,0 +1,8 @@ +VOID:OBJECT +VOID:VOID +VOID:PARAM +VOID:POINTER +VOID:UINT +VOID:UINT,UINT +VOID:OBJECT,OBJECT +VOID:STRING,OBJECT diff --git a/src/tidy/tidy-private.h b/src/tidy/tidy-private.h new file mode 100644 index 000000000..5f17d939b --- /dev/null +++ b/src/tidy/tidy-private.h @@ -0,0 +1,40 @@ +/* tidy-private.h: Private declarations + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __TIDY_PRIVATE_H__ +#define __TIDY_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +#define I_(str) (g_intern_static_string ((str))) + +#define TIDY_PARAM_READABLE \ + (G_PARAM_READABLE | \ + G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB) + +#define TIDY_PARAM_READWRITE \ + (G_PARAM_READABLE | G_PARAM_WRITABLE | \ + G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB) + +G_END_DECLS + +#endif /* __TIDY_PRIVATE_H__ */ diff --git a/src/tidy/tidy-stylable.c b/src/tidy/tidy-stylable.c new file mode 100644 index 000000000..47fece87e --- /dev/null +++ b/src/tidy/tidy-stylable.c @@ -0,0 +1,789 @@ +/* tidy-stylable.c: Interface for stylable objects + */ + +/** + * SECTION:tidy-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 #TidyStyle to them. + * + * Objects can choose to subclass #TidyActor, and thus inherit all the + * #TidyActor style properties; or they can subclass #TidyActor and + * reimplement the #TidyStylable interface to add new style properties + * specific for them (and their subclasses); or, finally, they can simply + * subclass #GObject and implement #TidyStylable to install new properties. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include + +#include "tidy-marshal.h" +#include "tidy-private.h" +#include "tidy-stylable.h" + +enum +{ + STYLE_SET, + STYLE_NOTIFY, + + 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 +tidy_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 +tidy_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 +tidy_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 ("tidy-stylable-real-owner-quark"); + quark_style = g_quark_from_static_string ("tidy-stylable-style-quark"); + + style_property_spec_pool = g_param_spec_pool_new (FALSE); + + property_notify_context.quark_notify_queue = g_quark_from_static_string ("TidyStylable-style-property-notify-queue"); + property_notify_context.dispatcher = tidy_stylable_notify_dispatcher; + + /** + * TidyStylable:style: + * + * The #TidyStyle attached to a stylable object. + */ + g_object_interface_install_property (g_iface, + g_param_spec_object ("style", + "Style", + "A style object", + TIDY_TYPE_STYLE, + TIDY_PARAM_READWRITE)); + + /** + * TidyStylable::style-set: + * @stylable: the #TidyStylable that received the signal + * @old_style: the previously set #TidyStyle for @stylable + * + * The ::style-set signal is emitted each time the #TidyStyle attached + * to @stylable has been changed. + */ + stylable_signals[STYLE_SET] = + g_signal_new (I_("style-set"), + iface_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TidyStylableIface, style_set), + NULL, NULL, + _tidy_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + TIDY_TYPE_STYLE); + 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 (TidyStylableIface, style_notify), + NULL, NULL, + _tidy_marshal_VOID__PARAM, + G_TYPE_NONE, 1, + G_TYPE_PARAM); + } +} + +GType +tidy_stylable_get_type (void) +{ + static GType our_type = 0; + + if (G_UNLIKELY (our_type == 0)) + { + GTypeInfo stylable_info = { + sizeof (TidyStylableIface), + tidy_stylable_base_init, + tidy_stylable_base_finalize + }; + + our_type = g_type_register_static (G_TYPE_INTERFACE, + I_("TidyStylable"), + &stylable_info, 0); + } + + return our_type; +} + +void +tidy_stylable_freeze_notify (TidyStylable *stylable) +{ + g_return_if_fail (TIDY_IS_STYLABLE (stylable)); + + g_object_ref (stylable); + g_object_notify_queue_freeze (G_OBJECT (stylable), &property_notify_context); + g_object_unref (stylable); +} + +void +tidy_stylable_thaw_notify (TidyStylable *stylable) +{ + GObjectNotifyQueue *nqueue; + + g_return_if_fail (TIDY_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 +tidy_stylable_notify (TidyStylable *stylable, + const gchar *property_name) +{ + GParamSpec *pspec; + + g_return_if_fail (TIDY_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); +} + +/** + * tidy_stylable_iface_install_property: + * @iface: a #TidyStylableIface + * @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 #TidyStylableIface initialization + * function of a class, for instance: + * + * + * G_DEFINE_TYPE_WITH_CODE (FooActor, foo_actor, CLUTTER_TYPE_ACTOR, + * G_IMPLEMENT_INTERFACE (TIDY_TYPE_STYLABLE, + * tidy_stylable_init)); + * ... + * static void + * tidy_stylable_init (TidyStylableIface *iface) + * { + * static gboolean is_initialized = FALSE; + * + * if (!is_initialized) + * { + * ... + * tidy_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 +tidy_stylable_iface_install_property (TidyStylableIface *iface, + GType owner_type, + GParamSpec *pspec) +{ + g_return_if_fail (TIDY_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); +} + +/** + * tidy_stylable_list_properties: + * @stylable: a #TidyStylable + * @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 ** +tidy_stylable_list_properties (TidyStylable *stylable, + guint *n_props) +{ + GParamSpec **pspecs = NULL; + guint n; + + g_return_val_if_fail (TIDY_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; +} + +/** + * tidy_stylable_find_property: + * @stylable: a #TidyStylable + * @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 * +tidy_stylable_find_property (TidyStylable *stylable, + const gchar *property_name) +{ + g_return_val_if_fail (TIDY_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 +tidy_stylable_set_property_internal (TidyStylable *stylable, + GParamSpec *pspec, + const GValue *value, + GObjectNotifyQueue *nqueue) +{ + GValue tmp_value = { 0, }; + + g_value_init (&tmp_value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + + if (!g_value_transform (value, &tmp_value)) + g_warning ("unable to set property `%s' of type `%s' from value of type `%s'", + pspec->name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)), + G_VALUE_TYPE_NAME (value)); + else if (g_param_value_validate (pspec, &tmp_value) && + !(pspec->flags & G_PARAM_LAX_VALIDATION)) + { + gchar *contents = g_strdup_value_contents (value); + + g_warning ("value \"%s\" of type `%s' is invalid or out of range for property `%s' of type `%s'", + contents, + G_VALUE_TYPE_NAME (value), + pspec->name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + g_free (contents); + } + else + { + TidyStyle *style = tidy_stylable_get_style (stylable); + gchar *real_name; + + real_name = g_strconcat (g_param_spec_get_qdata (pspec, quark_real_owner), + "::", + pspec->name, + NULL); + + if (!tidy_style_has_property (style, real_name)) + tidy_style_add_property (style, real_name, + G_PARAM_SPEC_VALUE_TYPE (pspec)); + + tidy_style_set_property (style, real_name, &tmp_value); + g_object_notify_queue_add (G_OBJECT (stylable), nqueue, pspec); + + g_free (real_name); + } + + g_value_unset (&tmp_value); +} + +static inline void +tidy_stylable_get_property_internal (TidyStylable *stylable, + GParamSpec *pspec, + GValue *value) +{ + TidyStyle *style; + GValue real_value = { 0, }; + gchar *real_name; + + real_name = g_strconcat (g_param_spec_get_qdata (pspec, quark_real_owner), + "::", + pspec->name, + NULL); + + style = tidy_stylable_get_style (stylable); + if (!tidy_style_has_property (style, real_name)) + { + /* the style has no property set, use the default value + * from the GParamSpec + */ + g_param_value_set_default (pspec, value); + g_free (real_name); + return; + } + + tidy_style_get_property (style, real_name, &real_value); + + g_value_copy (&real_value, value); + g_value_unset (&real_value); + + g_free (real_name); +} + +/** + * tidy_stylable_get_property: + * @stylable: a #TidyStylable + * @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 +tidy_stylable_get_property (TidyStylable *stylable, + const gchar *property_name, + GValue *value) +{ + GParamSpec *pspec; + + g_return_if_fail (TIDY_IS_STYLABLE (stylable)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (value != NULL); + + pspec = tidy_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; + } + + tidy_stylable_get_property_internal (stylable, pspec, value); +} + +/** + * tidy_stylable_set_property: + * @stylable: a #TidyStylable + * @property_name: the name of the property to set + * @value: an initialized #GValue + * + * Sets the property @property_name with @value. + */ +void +tidy_stylable_set_property (TidyStylable *stylable, + const gchar *property_name, + const GValue *value) +{ + GObjectNotifyQueue *nqueue; + GParamSpec *pspec; + + g_return_if_fail (TIDY_IS_STYLABLE (stylable)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (value != NULL); + + g_object_ref (stylable); + + nqueue = g_object_notify_queue_freeze (G_OBJECT (stylable), + &property_notify_context); + + pspec = tidy_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); + } + else if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("Style property `%s' of class `%s' is not readable", + pspec->name, + g_type_name (G_OBJECT_TYPE (stylable))); + } + else 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))); + } + else + tidy_stylable_set_property_internal (stylable, pspec, value, nqueue); + + g_object_notify_queue_thaw (G_OBJECT (stylable), nqueue); + g_object_unref (stylable); +} + +static void +tidy_stylable_get_valist (TidyStylable *stylable, + const gchar *first_property_name, + va_list varargs) +{ + const gchar *name; + + g_object_ref (stylable); + + name = first_property_name; + + while (name) + { + GParamSpec *pspec; + GValue value = { 0, }; + gchar *error; + + pspec = tidy_stylable_find_property (stylable, name); + if (!pspec) + { + g_warning ("%s: no style property named `%s' found for class `%s'", + G_STRLOC, + name, + g_type_name (G_OBJECT_TYPE (stylable))); + break; + } + + 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))); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + tidy_stylable_get_property_internal (stylable, pspec, &value); + + G_VALUE_LCOPY (&value, varargs, 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 (varargs, gchar*); + } + + g_object_unref (stylable); +} + +static void +tidy_stylable_set_valist (TidyStylable *stylable, + const gchar *first_property_name, + va_list varargs) +{ + GObjectNotifyQueue *nqueue; + const gchar *name; + + g_object_ref (stylable); + + nqueue = g_object_notify_queue_freeze (G_OBJECT (stylable), + &property_notify_context); + + name = first_property_name; + + while (name) + { + GParamSpec *pspec; + GValue value = { 0, }; + gchar *error; + + pspec = tidy_stylable_find_property (stylable, name); + if (!pspec) + { + g_warning ("%s: no style property named `%s' found for class `%s'", + G_STRLOC, + name, + g_type_name (G_OBJECT_TYPE (stylable))); + break; + } + + if (!(pspec->flags & G_PARAM_WRITABLE) || + (pspec->flags & G_PARAM_CONSTRUCT_ONLY)) + { + g_warning ("Style property `%s' of class `%s' is not writable", + pspec->name, + g_type_name (G_OBJECT_TYPE (stylable))); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + + G_VALUE_COLLECT (&value, varargs, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + g_value_unset (&value); + break; + } + + tidy_stylable_set_property_internal (stylable, pspec, &value, nqueue); + g_value_unset (&value); + + name = va_arg (varargs, gchar*); + } + + g_object_notify_queue_thaw (G_OBJECT (stylable), nqueue); + g_object_unref (stylable); +} + +/** + * tidy_stylable_get: + * @stylable: a #TidyStylable + * @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 tidy_stylable_get(<!-- -->) + * An example of using tidy_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; + * + * tidy_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 +tidy_stylable_get (TidyStylable *stylable, + const gchar *first_property_name, + ...) +{ + va_list args; + + g_return_if_fail (TIDY_IS_STYLABLE (stylable)); + g_return_if_fail (first_property_name != NULL); + + va_start (args, first_property_name); + tidy_stylable_get_valist (stylable, first_property_name, args); + va_end (args); +} + +/** + * tidy_stylable_set: + * @stylable: a #TidyStylable + * @first_property_name: name of the first property to set + * @Varargs: value for the first property, followed optionally by + * more name/value pairs, followed by %NULL + * + * Sets the style properties of @stylable. + */ +void +tidy_stylable_set (TidyStylable *stylable, + const gchar *first_property_name, + ...) +{ + va_list args; + + g_return_if_fail (TIDY_IS_STYLABLE (stylable)); + g_return_if_fail (first_property_name != NULL); + + va_start (args, first_property_name); + tidy_stylable_set_valist (stylable, first_property_name, args); + va_end (args); +} + +/** + * tidy_stylable_get_style: + * @stylable: a #TidyStylable + * + * Retrieves the #TidyStyle used by @stylable. This function does not + * alter the reference count of the returned object. + * + * Return value: a #TidyStyle + */ +TidyStyle * +tidy_stylable_get_style (TidyStylable *stylable) +{ + TidyStylableIface *iface; + + g_return_val_if_fail (TIDY_IS_STYLABLE (stylable), NULL); + + iface = TIDY_STYLABLE_GET_IFACE (stylable); + if (iface->get_style) + return iface->get_style (stylable); + + return g_object_get_data (G_OBJECT (stylable), "tidy-stylable-style"); +} + +/** + * tidy_stylable_set_style: + * @stylable: a #TidyStylable + * @style: a #TidyStyle + * + * Sets @style as the new #TidyStyle to be used by @stylable. + * + * The #TidyStylable will take ownership of the passed #TidyStyle. + * + * After the #TidyStle has been set, the TidyStylable::style-set signal + * will be emitted. + */ +void +tidy_stylable_set_style (TidyStylable *stylable, + TidyStyle *style) +{ + TidyStylableIface *iface; + TidyStyle *old_style; + + g_return_if_fail (TIDY_IS_STYLABLE (stylable)); + g_return_if_fail (TIDY_IS_STYLE (style)); + + iface = TIDY_STYLABLE_GET_IFACE (stylable); + + old_style = tidy_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_SET], 0, old_style); + g_object_unref (old_style); + + g_object_notify (G_OBJECT (stylable), "style"); +} diff --git a/src/tidy/tidy-stylable.h b/src/tidy/tidy-stylable.h new file mode 100644 index 000000000..fb0547f75 --- /dev/null +++ b/src/tidy/tidy-stylable.h @@ -0,0 +1,69 @@ +#ifndef __TIDY_STYLABLE_H__ +#define __TIDY_STYLABLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define TIDY_TYPE_STYLABLE (tidy_stylable_get_type ()) +#define TIDY_STYLABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIDY_TYPE_STYLABLE, TidyStylable)) +#define TIDY_IS_STYLABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TIDY_TYPE_STYLABLE)) +#define TIDY_STYLABLE_IFACE(iface) (G_TYPE_CHECK_CLASS_CAST ((iface), TIDY_TYPE_STYLABLE, TidyStylableIface)) +#define TIDY_IS_STYLABLE_IFACE(iface) (G_TYPE_CHECK_CLASS_TYPE ((iface), TIDY_TYPE_STYLABLE)) +#define TIDY_STYLABLE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), TIDY_TYPE_STYLABLE, TidyStylableIface)) + +typedef struct _TidyStylable TidyStylable; /* dummy typedef */ +typedef struct _TidyStylableIface TidyStylableIface; + +struct _TidyStylableIface +{ + GTypeInterface g_iface; + + /* virtual functions */ + TidyStyle *(* get_style) (TidyStylable *stylable); + void (* set_style) (TidyStylable *stylable, + TidyStyle *style); + + /* signals, not vfuncs */ + void (* style_notify) (TidyStylable *stylable, + GParamSpec *pspec); + + void (* style_set) (TidyStylable *stylable, + TidyStyle *old_style); +}; + +GType tidy_stylable_get_type (void) G_GNUC_CONST; + +void tidy_stylable_iface_install_property (TidyStylableIface *iface, + GType owner_type, + GParamSpec *pspec); + +void tidy_stylable_freeze_notify (TidyStylable *stylable); +void tidy_stylable_notify (TidyStylable *stylable, + const gchar *property_name); +void tidy_stylable_thaw_notify (TidyStylable *stylable); +GParamSpec **tidy_stylable_list_properties (TidyStylable *stylable, + guint *n_props); +GParamSpec * tidy_stylable_find_property (TidyStylable *stylable, + const gchar *property_name); +void tidy_stylable_set_style (TidyStylable *stylable, + TidyStyle *style); +TidyStyle * tidy_stylable_get_style (TidyStylable *stylable); + +void tidy_stylable_set (TidyStylable *stylable, + const gchar *first_property_name, + ...) G_GNUC_NULL_TERMINATED; +void tidy_stylable_get (TidyStylable *stylable, + const gchar *first_property_name, + ...) G_GNUC_NULL_TERMINATED; +void tidy_stylable_set_property (TidyStylable *stylable, + const gchar *property_name, + const GValue *value); +void tidy_stylable_get_property (TidyStylable *stylable, + const gchar *property_name, + GValue *value); + +G_END_DECLS + +#endif /* __TIDY_STYLABLE_H__ */ diff --git a/src/tidy/tidy-style.c b/src/tidy/tidy-style.c new file mode 100644 index 000000000..f59ef5b29 --- /dev/null +++ b/src/tidy/tidy-style.c @@ -0,0 +1,653 @@ +#ifndef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include +#include + +#include "tidy-style.h" +#include "tidy-marshal.h" +#include "tidy-debug.h" + +enum +{ + CHANGED, + + LAST_SIGNAL +}; + +#define TIDY_STYLE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TIDY_TYPE_STYLE, TidyStylePrivate)) + +typedef struct { + GType value_type; + gchar *value_name; + GValue value; +} StyleProperty; + +typedef struct { + gchar *name; + GType behaviour_type; + GArray *parameters; + guint duration; + ClutterAlphaFunc alpha_func; +} StyleEffect; + +struct _TidyStylePrivate +{ + GHashTable *properties; + GHashTable *effects; +}; + +static guint style_signals[LAST_SIGNAL] = { 0, }; + +static const gchar *tidy_default_font_name = "Sans 12px"; +static const ClutterColor tidy_default_text_color = { 0x00, 0x00, 0x00, 0xff }; +static const ClutterColor tidy_default_bg_color = { 0xcc, 0xcc, 0xcc, 0xff }; +static const ClutterColor tidy_default_active_color = { 0xf5, 0x79, 0x00, 0xff }; + +static TidyStyle *default_style = NULL; + +G_DEFINE_TYPE (TidyStyle, tidy_style, G_TYPE_OBJECT); + +static StyleProperty * +style_property_new (const gchar *value_name, + GType value_type) +{ + StyleProperty *retval; + + retval = g_slice_new0 (StyleProperty); + retval->value_type = value_type; + retval->value_name = g_strdup (value_name); + g_value_init (&retval->value, value_type); + + return retval; +} + +static void +style_property_free (gpointer data) +{ + if (G_LIKELY (data)) + { + StyleProperty *sp = data; + + g_free (sp->value_name); + g_value_unset (&sp->value); + } +} + +static StyleEffect * +style_effect_new (const gchar *name) +{ + StyleEffect *retval; + + retval = g_slice_new0 (StyleEffect); + retval->name = g_strdup (name); + retval->behaviour_type = G_TYPE_INVALID; + + return retval; +} + +static void +style_effect_free (gpointer data) +{ + if (G_LIKELY (data)) + { + StyleEffect *effect = data; + + g_free (effect->name); + + if (effect->parameters) + { + gint i; + + for (i = 0; i < effect->parameters->len; i++) + { + GParameter *param; + + param = &g_array_index (effect->parameters, GParameter, i); + + g_free ((gchar *) param->name); + g_value_unset (¶m->value); + } + + g_array_free (effect->parameters, TRUE); + effect->parameters = NULL; + } + + g_slice_free (StyleEffect, effect); + } +} + +static void +init_defaults (TidyStyle *style) +{ + TidyStylePrivate *priv = style->priv; + + { + StyleProperty *sp; + + sp = style_property_new (TIDY_FONT_NAME, G_TYPE_STRING); + g_value_set_string (&sp->value, tidy_default_font_name); + + g_hash_table_insert (priv->properties, sp->value_name, sp); + } + + { + StyleProperty *sp; + + sp = style_property_new (TIDY_BACKGROUND_COLOR, CLUTTER_TYPE_COLOR); + g_value_set_boxed (&sp->value, &tidy_default_bg_color); + + g_hash_table_insert (priv->properties, sp->value_name, sp); + } + + { + StyleProperty *sp; + + sp = style_property_new (TIDY_ACTIVE_COLOR, CLUTTER_TYPE_COLOR); + g_value_set_boxed (&sp->value, &tidy_default_active_color); + + g_hash_table_insert (priv->properties, sp->value_name, sp); + } + + { + StyleProperty *sp; + + sp = style_property_new (TIDY_TEXT_COLOR, CLUTTER_TYPE_COLOR); + g_value_set_boxed (&sp->value, &tidy_default_text_color); + + g_hash_table_insert (priv->properties, sp->value_name, sp); + } +} + +static gboolean +tidy_style_load_from_file (TidyStyle *style, + const gchar *filename, + GError **error) +{ + GKeyFile *rc_file; + GError *internal_error; + + rc_file = g_key_file_new (); + + internal_error = NULL; + g_key_file_load_from_file (rc_file, filename, 0, &internal_error); + if (internal_error) + { + g_key_file_free (rc_file); + /* if the specified files does not exist then just ignore it + * and fall back to the default values; if, instead, the file + * is not accessible or is malformed, propagate the error + */ + if (internal_error->domain == G_FILE_ERROR && + internal_error->code == G_FILE_ERROR_NOENT) + { + g_error_free (internal_error); + return TRUE; + } + + g_propagate_error (error, internal_error); + return FALSE; + } + + g_key_file_free (rc_file); + + return TRUE; +} + +static void +tidy_style_load (TidyStyle *style) +{ + const gchar *env_var; + gchar *rc_file = NULL; + GError *error; + + init_defaults (style); + + env_var = g_getenv ("TIDY_RC_FILE"); + if (env_var && *env_var) + rc_file = g_strdup (env_var); + + if (!rc_file) + rc_file = g_build_filename (g_get_user_config_dir (), + "tidy", + "tidyrc", + NULL); + + error = NULL; + if (!tidy_style_load_from_file (style, rc_file, &error)) + { + g_critical ("Unable to load resource file `%s': %s", + rc_file, + error->message); + g_error_free (error); + } + + g_free (rc_file); +} + +static void +tidy_style_finalize (GObject *gobject) +{ + TidyStylePrivate *priv = TIDY_STYLE (gobject)->priv; + + g_hash_table_destroy (priv->properties); + g_hash_table_destroy (priv->effects); + + G_OBJECT_CLASS (tidy_style_parent_class)->finalize (gobject); +} + +static void +tidy_style_class_init (TidyStyleClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TidyStylePrivate)); + + gobject_class->finalize = tidy_style_finalize; + + style_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TidyStyleClass, changed), + NULL, NULL, + _tidy_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +tidy_style_init (TidyStyle *style) +{ + TidyStylePrivate *priv; + + style->priv = priv = TIDY_STYLE_GET_PRIVATE (style); + + priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + style_property_free); + priv->effects = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + style_effect_free); + + tidy_style_load (style); +} + +/* need to unref */ +TidyStyle * +tidy_style_new (void) +{ + return g_object_new (TIDY_TYPE_STYLE, NULL); +} + +/* never ref/unref */ +TidyStyle * +tidy_style_get_default (void) +{ + if (G_LIKELY (default_style)) + return default_style; + + default_style = g_object_new (TIDY_TYPE_STYLE, NULL); + + return default_style; +} + +static StyleProperty * +tidy_style_find_property (TidyStyle *style, + const gchar *property_name) +{ + return g_hash_table_lookup (style->priv->properties, property_name); +} + +gboolean +tidy_style_has_property (TidyStyle *style, + const gchar *property_name) +{ + g_return_val_if_fail (TIDY_IS_STYLE (style), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + + return (tidy_style_find_property (style, property_name) != NULL); +} + +void +tidy_style_add_property (TidyStyle *style, + const gchar *property_name, + GType property_type) +{ + StyleProperty *property; + + g_return_if_fail (TIDY_IS_STYLE (style)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (property_type != G_TYPE_INVALID); + + property = tidy_style_find_property (style, property_name); + if (G_UNLIKELY (property)) + { + g_warning ("A property named `%s', with type %s already exists.", + property->value_name, + g_type_name (property->value_type)); + return; + } + + property = style_property_new (property_name, property_type); + g_hash_table_insert (style->priv->properties, property->value_name, property); + + g_signal_emit (style, style_signals[CHANGED], 0); +} + +void +tidy_style_get_property (TidyStyle *style, + const gchar *property_name, + GValue *value) +{ + StyleProperty *property; + + g_return_if_fail (TIDY_IS_STYLE (style)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (value != NULL); + + property = tidy_style_find_property (style, property_name); + if (!property) + { + g_warning ("No style property named `%s' found.", property_name); + return; + } + + g_value_init (value, property->value_type); + g_value_copy (&property->value, value); +} + +void +tidy_style_set_property (TidyStyle *style, + const gchar *property_name, + const GValue *value) +{ + StyleProperty *property; + + g_return_if_fail (TIDY_IS_STYLE (style)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (value != NULL); + + property = tidy_style_find_property (style, property_name); + if (!property) + { + g_warning ("No style property named `%s' found.", property_name); + return; + } + + g_value_copy (value, &property->value); + + g_signal_emit (style, style_signals[CHANGED], 0); +} + +static StyleEffect * +tidy_style_find_effect (TidyStyle *style, + const gchar *effect_name) +{ + return g_hash_table_lookup (style->priv->effects, effect_name); +} + +void +tidy_style_add_effect (TidyStyle *style, + const gchar *effect_name) +{ + StyleEffect *effect; + + effect = tidy_style_find_effect (style, effect_name); + if (G_UNLIKELY (effect)) + { + g_warning ("An effect named `%s', with type %s already exists.", + effect->name, + g_type_name (effect->behaviour_type)); + return; + } + + effect = style_effect_new (effect_name); + g_hash_table_replace (style->priv->effects, + effect->name, + effect); +} + +gboolean +tidy_style_has_effect (TidyStyle *style, + const gchar *effect_name) +{ + g_return_val_if_fail (TIDY_IS_STYLE (style), FALSE); + g_return_val_if_fail (effect_name != NULL, FALSE); + + return (tidy_style_find_effect (style, effect_name) != NULL); +} + +static void +tidy_style_set_effect_valist (TidyStyle *style, + StyleEffect *effect, + const gchar *first_property_name, + va_list varargs) +{ + GObjectClass *klass; + const gchar *name; + + klass = g_type_class_ref (effect->behaviour_type); + if (G_UNLIKELY (!klass)) + return; + + name = first_property_name; + while (name) + { + GParamSpec *pspec; + GParameter param = { 0, }; + GValue value = { 0, }; + gchar *error = NULL; + + pspec = g_object_class_find_property (klass, name); + if (!pspec) + { + g_warning ("Unable to find the property `%s' for the " + "behaviour of type `%s'", + name, + g_type_name (effect->behaviour_type)); + break; + } + + if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("The property `%s' for the behaviour of type " + "`%s' is not writable", + pspec->name, + g_type_name (effect->behaviour_type)); + break; + } + + param.name = g_strdup (pspec->name); + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + + G_VALUE_COLLECT (&value, varargs, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + g_value_unset (&value); + break; + } + + g_value_init (&(param.value), G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_value_copy (&value, &(param.value)); + g_value_unset (&value); + + name = va_arg (varargs, gchar*); + } + + g_type_class_unref (klass); +} + +void +tidy_style_set_effect (TidyStyle *style, + const gchar *effect_name, + guint duration, + GType behaviour_type, + ClutterAlphaFunc alpha_func, + const gchar *first_property_name, + ...) +{ + StyleEffect *effect; + va_list args; + + effect = tidy_style_find_effect (style, effect_name); + if (!effect) + { + g_warning ("No effect named `%s' found.", effect_name); + return; + } + + if (effect->parameters) + { + gint i; + + for (i = 0; i < effect->parameters->len; i++) + { + GParameter *param; + + param = &g_array_index (effect->parameters, GParameter, i); + + g_free ((gchar *) param->name); + g_value_unset (¶m->value); + } + + g_array_free (effect->parameters, TRUE); + effect->parameters = NULL; + } + + effect->duration = duration; + effect->behaviour_type = behaviour_type; + effect->alpha_func = alpha_func; + effect->parameters = g_array_new (FALSE, FALSE, sizeof (GParameter)); + + va_start (args, first_property_name); + tidy_style_set_effect_valist (style, effect, first_property_name, args); + va_end (args); +} + +void +tidy_style_set_effectv (TidyStyle *style, + const gchar *effect_name, + guint duration, + GType behaviour_type, + ClutterAlphaFunc alpha_func, + guint n_parameters, + GParameter *parameters) +{ + StyleEffect *effect; + gint i; + + effect = tidy_style_find_effect (style, effect_name); + if (!effect) + { + g_warning ("No effect named `%s' found.", effect_name); + return; + } + + if (effect->parameters) + { + gint i; + + for (i = 0; i < effect->parameters->len; i++) + { + GParameter *param; + + param = &g_array_index (effect->parameters, GParameter, i); + + g_free ((gchar *) param->name); + g_value_unset (¶m->value); + } + + g_array_free (effect->parameters, TRUE); + effect->parameters = NULL; + } + + effect->duration = duration; + effect->behaviour_type = behaviour_type; + effect->alpha_func = alpha_func; + effect->parameters = g_array_new (FALSE, FALSE, sizeof (GParameter)); + + for (i = 0; i < n_parameters; i++) + { + GParameter param = { NULL, }; + + param.name = g_strdup (parameters[i].name); + + g_value_init (¶m.value, G_VALUE_TYPE (¶meters[i].value)); + g_value_copy (¶meters[i].value, ¶m.value); + + g_array_append_val (effect->parameters, param); + } +} + +static ClutterBehaviour * +tidy_style_construct_effect (TidyStyle *style, + const gchar *effect_name) +{ + ClutterTimeline *timeline; + ClutterAlpha *alpha; + ClutterBehaviour *behaviour; + StyleEffect *effect; + + effect = tidy_style_find_effect (style, effect_name); + if (!effect) + { + g_warning ("No effect named `%s' found.", effect_name); + return NULL; + } + + timeline = clutter_timeline_new_for_duration (effect->duration); + + alpha = clutter_alpha_new_full (timeline, effect->alpha_func, NULL, NULL); + g_object_unref (timeline); + + behaviour = g_object_newv (effect->behaviour_type, + effect->parameters->len, + (GParameter *) effect->parameters->data); + + clutter_behaviour_set_alpha (behaviour, alpha); + + /* we just unref the behaviour, which will take care of cleaning + * up everything (alpha+timeline) + */ + g_signal_connect_swapped (timeline, + "completed", G_CALLBACK (g_object_unref), + behaviour); + + return behaviour; +} + +ClutterTimeline * +tidy_style_get_effect (TidyStyle *style, + const gchar *effect_name, + ClutterActor *actor) +{ + ClutterBehaviour *behaviour; + ClutterAlpha *alpha; + ClutterTimeline *timeline; + + g_return_val_if_fail (TIDY_IS_STYLE (style), NULL); + g_return_val_if_fail (effect_name != NULL, NULL); + g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL); + + behaviour = tidy_style_construct_effect (style, effect_name); + if (!behaviour) + return NULL; + + clutter_behaviour_apply (behaviour, actor); + + alpha = clutter_behaviour_get_alpha (behaviour); + timeline = clutter_alpha_get_timeline (alpha); + + return timeline; +} diff --git a/src/tidy/tidy-style.h b/src/tidy/tidy-style.h new file mode 100644 index 000000000..001c1ddeb --- /dev/null +++ b/src/tidy/tidy-style.h @@ -0,0 +1,85 @@ +#ifndef __TIDY_STYLE_H__ +#define __TIDY_STYLE_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define TIDY_TYPE_STYLE (tidy_style_get_type ()) +#define TIDY_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIDY_TYPE_STYLE, TidyStyle)) +#define TIDY_IS_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TIDY_TYPE_STYLE)) +#define TIDY_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TIDY_TYPE_STYLE, TidyStyleClass)) +#define TIDY_IS_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TIDY_TYPE_STYLE)) +#define TIDY_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TIDY_TYPE_STYLE, TidyStyleClass)) + +/* Default properties */ +#define TIDY_FONT_NAME "TidyActor::font-name" +#define TIDY_BACKGROUND_COLOR "TidyActor::bg-color" +#define TIDY_ACTIVE_COLOR "TidyActor::active-color" +#define TIDY_TEXT_COLOR "TidyActor::text-color" + +typedef struct _TidyStyle TidyStyle; +typedef struct _TidyStylePrivate TidyStylePrivate; +typedef struct _TidyStyleClass TidyStyleClass; + +struct _TidyStyle +{ + GObject parent_instance; + + TidyStylePrivate *priv; +}; + +struct _TidyStyleClass +{ + GObjectClass parent_class; + + void (* changed) (TidyStyle *style); +}; + +GType tidy_style_get_type (void) G_GNUC_CONST; + +TidyStyle * tidy_style_get_default (void); +TidyStyle * tidy_style_new (void); + +gboolean tidy_style_has_property (TidyStyle *style, + const gchar *property_name); +gboolean tidy_style_has_effect (TidyStyle *style, + const gchar *effect_name); +void tidy_style_add_property (TidyStyle *style, + const gchar *property_name, + GType property_type); +void tidy_style_add_effect (TidyStyle *style, + const gchar *effect_name); + +void tidy_style_get_property (TidyStyle *style, + const gchar *property_name, + GValue *value); +void tidy_style_set_property (TidyStyle *style, + const gchar *property_name, + const GValue *value); + +ClutterTimeline *tidy_style_get_effect (TidyStyle *style, + const gchar *effect_name, + ClutterActor *actor); +void tidy_style_set_effectv (TidyStyle *style, + const gchar *effect_name, + guint duration, + GType behaviour_type, + ClutterAlphaFunc alpha_func, + guint n_parameters, + GParameter *parameters); +void tidy_style_set_effect (TidyStyle *style, + const gchar *effect_name, + guint duration, + GType behaviour_type, + ClutterAlphaFunc alpha_func, + const gchar *first_property_name, + ...) G_GNUC_NULL_TERMINATED; + + +G_END_DECLS + +#endif /* __TIDY_STYLE_H__ */ diff --git a/src/tidy/tidy-types.h b/src/tidy/tidy-types.h new file mode 100644 index 000000000..cfe55923d --- /dev/null +++ b/src/tidy/tidy-types.h @@ -0,0 +1,25 @@ +#ifndef __TIDY_TYPES_H__ +#define __TIDY_TYPES_H__ + +#include +#include + +G_BEGIN_DECLS + +#define TIDY_TYPE_PADDING (tidy_padding_get_type ()) + +typedef struct _TidyPadding TidyPadding; + +struct _TidyPadding +{ + ClutterUnit top; + ClutterUnit right; + ClutterUnit bottom; + ClutterUnit left; +}; + +GType tidy_padding_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __TIDY_TYPES_H__ */ diff --git a/src/tidy/tidy-util.c b/src/tidy/tidy-util.c new file mode 100644 index 000000000..e2c622fb9 --- /dev/null +++ b/src/tidy/tidy-util.c @@ -0,0 +1,37 @@ +#include "tidy-util.h" + +/* Hack to (mostly) fill glyph cache, useful on MBX. + * + * FIXME: untested +*/ +void +tidy_util_preload_glyphs (char *font, ...) +{ + va_list args; + + va_start (args, font); + + while (font) + { + /* Hold on to your hat.. */ + ClutterActor *foo; + ClutterColor text_color = { 0xff, 0xff, 0xff, 0xff }; + + foo = clutter_label_new_full + (font, + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "1234567890&()*.,';:-_+=[]{}#@?><\"!`%\\|/ ", + &text_color); + if (foo) + { + clutter_actor_realize(foo); + clutter_actor_paint(foo); + g_object_unref (foo); + } + + font = va_arg (args, char*); + } + + va_end (args); +} diff --git a/src/tidy/tidy-util.h b/src/tidy/tidy-util.h new file mode 100644 index 000000000..8c8fa11fb --- /dev/null +++ b/src/tidy/tidy-util.h @@ -0,0 +1,9 @@ +#ifndef _TIDY_UTIL +#define _TIDY_UTIL + +#include + +void +tidy_util_preload_glyphs (char *font, ...); + +#endif