#include "cb-button.h" /** * SECTION:cb-button * @short_description: Button widget * * A button widget with support for a text label and background color. */ /* convenience macro for GType implementations; see: * http://library.gnome.org/devel/gobject/2.27/gobject-Type-Information.html#G-DEFINE-TYPE:CAPS */ G_DEFINE_TYPE (CbButton, cb_button, CLUTTER_TYPE_ACTOR); /* macro for accessing the object's private structure */ #define CB_BUTTON_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CB_TYPE_BUTTON, CbButtonPrivate)) /* private structure - should only be accessed through the public API; * this is used to store member variables whose properties * need to be accessible from the implementation; for example, if we * intend to create wrapper functions which modify properties on the * actors composing an object, we should keep a reference to the actors * here * * this is also the place where other state variables go: * for example, you might record the current state of the button * (toggled on or off) or a background image */ struct _CbButtonPrivate { ClutterActor *child; ClutterActor *label; ClutterAction *click_action; gchar *text; }; /* enumerates property identifiers for this class; * note that property identifiers should be non-zero integers, * so we add an unused PROP_0 to occupy the 0 position in the enum */ enum { PROP_0, PROP_TEXT }; /* enumerates signal identifiers for this class; * LAST_SIGNAL is not used as a signal identifier, but is instead * used to delineate the size of the cache array for signals (see below) */ enum { CLICKED, LAST_SIGNAL }; /* cache array for signals */ static guint cb_button_signals[LAST_SIGNAL] = { 0, }; /* from http://mail.gnome.org/archives/gtk-devel-list/2004-July/msg00158.html: * * "The finalize method finishes releasing the remaining * resources just before the object itself will be freed from memory, and * therefore it will only be called once. The two step process helps break * cyclic references. Both dispose and finalize must chain up to their * parent objects by calling their parent's respective methods *after* they * have disposed or finalized their own members." */ static void cb_button_finalize (GObject *gobject) { CbButtonPrivate *priv = CB_BUTTON (gobject)->priv; g_free (priv->text); /* call the parent class' finalize() method */ G_OBJECT_CLASS (cb_button_parent_class)->finalize (gobject); } /* enables objects to be uniformly treated as GObjects; * also exposes properties so they become scriptable, e.g. * through ClutterScript */ static void cb_button_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { CbButton *button = CB_BUTTON (gobject); switch (prop_id) { case PROP_TEXT: cb_button_set_text (button, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } /* enables objects to be uniformly treated as GObjects */ static void cb_button_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { CbButtonPrivate *priv = CB_BUTTON (gobject)->priv; switch (prop_id) { case PROP_TEXT: g_value_set_string (value, priv->text); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } /* ClutterActor implementation * * we only implement destroy(), get_preferred_height(), get_preferred_width(), * allocate(), and paint(), as this is the minimum we can get away with */ /* composite actors should implement destroy(), and inside their * implementation destroy any actors they are composed from; * in this case, we just destroy the child ClutterBox */ static void cb_button_destroy (ClutterActor *self) { CbButtonPrivate *priv = CB_BUTTON (self)->priv; /* we just destroy the child, and let the child * deal with destroying _its_ children; note that we have a guard * here in case the child has already been destroyed */ if (priv->child) { clutter_actor_destroy (priv->child); priv->child = NULL; } /* chain up to destroy() on the parent ClutterActorClass; * note that we check the parent class has a destroy() implementation * before calling it */ if (CLUTTER_ACTOR_CLASS (cb_button_parent_class)->destroy) CLUTTER_ACTOR_CLASS (cb_button_parent_class)->destroy (self); } /* get_preferred_height and get_preferred_width defer to the * internal ClutterBox, adding 20px padding on each axis; * min_*_p is the minimum height or width the actor should occupy * to be useful; natural_*_p is the height or width the actor * would occupy if not constrained * * note that if we required explicit sizing for CbButtons * (i.e. a developer must set their height and width), * we wouldn't need to implement these functions */ static void cb_button_get_preferred_height (ClutterActor *self, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { CbButtonPrivate *priv = CB_BUTTON (self)->priv; clutter_actor_get_preferred_height (priv->child, for_width, min_height_p, natural_height_p); *min_height_p += 20.0; *natural_height_p += 20.0; } static void cb_button_get_preferred_width (ClutterActor *self, gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p) { CbButtonPrivate *priv = CB_BUTTON (self)->priv; clutter_actor_get_preferred_width (priv->child, for_height, min_width_p, natural_width_p); *min_width_p += 20.0; *natural_width_p += 20.0; } /* use the actor's allocation for the ClutterBox */ static void cb_button_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { CbButtonPrivate *priv = CB_BUTTON (actor)->priv; ClutterActorBox child_box = { 0, }; /* set the allocation for the whole button */ CLUTTER_ACTOR_CLASS (cb_button_parent_class)->allocate (actor, box, flags); /* make the child (the ClutterBox) fill the parent; * note that this allocation box is relative to the * coordinates of the whole button actor, so we can't just * use the box passed into this function; instead, it * is adjusted to span the whole of the actor, from its * top-left corner (0,0) to its bottom-right corner * (width,height) */ child_box.x1 = 0.0; child_box.y1 = 0.0; child_box.x2 = clutter_actor_box_get_width (box); child_box.y2 = clutter_actor_box_get_height (box); clutter_actor_allocate (priv->child, &child_box, flags); } /* paint function implementation: just calls paint() on the ClutterBox */ static void cb_button_paint (ClutterActor *actor) { CbButtonPrivate *priv = CB_BUTTON (actor)->priv; clutter_actor_paint (priv->child); } /* proxy ClickAction signals so they become signals from the actor */ static void cb_button_clicked (ClutterClickAction *action, ClutterActor *actor, gpointer user_data) { /* emit signal via the cache array */ g_signal_emit (actor, cb_button_signals[CLICKED], 0); } /* GObject class and instance initialization functions; note that * these have been placed after the Clutter implementation, as * they refer to the static function implementations above */ /* class init: attach functions to superclasses, define properties * and signals */ static void cb_button_class_init (CbButtonClass *klass) { ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; gobject_class->finalize = cb_button_finalize; gobject_class->set_property = cb_button_set_property; gobject_class->get_property = cb_button_get_property; actor_class->destroy = cb_button_destroy; actor_class->get_preferred_height = cb_button_get_preferred_height; actor_class->get_preferred_width = cb_button_get_preferred_width; actor_class->allocate = cb_button_allocate; actor_class->paint = cb_button_paint; g_type_class_add_private (klass, sizeof (CbButtonPrivate)); /** * CbButton:text: * * The text shown on the #CbButton */ pspec = g_param_spec_string ("text", "Text", "Text of the button", NULL, G_PARAM_READWRITE); g_object_class_install_property (gobject_class, PROP_TEXT, pspec); /** * CbButton::clicked: * @button: the #CbButton that emitted the signal * * The ::clicked signal is emitted when the internal #ClutterClickAction * associated with a #CbButton emits its own ::clicked signal */ cb_button_signals[CLICKED] = g_signal_new ("clicked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (CbButtonClass, clicked), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } /* object init: create a private structure and pack * composed ClutterActors into it */ static void cb_button_init (CbButton *self) { CbButtonPrivate *priv; ClutterLayoutManager *layout; priv = self->priv = CB_BUTTON_GET_PRIVATE (self); clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE); /* the only child of this actor is a ClutterBox with a * ClutterBinLayout: painting and allocation of the actor basically * involves painting and allocating this child box */ layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, CLUTTER_BIN_ALIGNMENT_CENTER); priv->child = clutter_actor_new (); clutter_actor_set_layout_manager (priv->child, layout); /* set the parent of the ClutterBox to this instance */ clutter_actor_add_child (CLUTTER_ACTOR (self), priv->child); /* add text label to the button; see the ClutterText API docs * for more information about available properties */ priv->label = g_object_new (CLUTTER_TYPE_TEXT, "line-alignment", PANGO_ALIGN_CENTER, "ellipsize", PANGO_ELLIPSIZE_END, NULL); clutter_actor_add_child (priv->child, priv->label); /* add a ClutterClickAction on this actor, so we can proxy its * "clicked" signal into a signal from this actor */ priv->click_action = clutter_click_action_new (); clutter_actor_add_action (CLUTTER_ACTOR (self), priv->click_action); g_signal_connect (priv->click_action, "clicked", G_CALLBACK (cb_button_clicked), NULL); } /* public API */ /* examples of public API functions which wrap functions * on internal actors */ /** * cb_button_set_text: * @self: a #CbButton * @text: the text to display on the button * * Set the text on the button */ void cb_button_set_text (CbButton *self, const gchar *text) { CbButtonPrivate *priv; /* public API should check its arguments; * see also g_return_val_if_fail for functions which * return a value */ g_return_if_fail (CB_IS_BUTTON (self)); priv = self->priv; g_free (priv->text); if (text) priv->text = g_strdup (text); else priv->text = g_strdup (""); /* call a function on the ClutterText inside the layout */ clutter_text_set_text (CLUTTER_TEXT (priv->label), priv->text); } /** * cb_button_set_background_color: * @self: a #CbButton * @color: the #ClutterColor to use for the button's background * * Set the color of the button's background */ void cb_button_set_background_color (CbButton *self, const ClutterColor *color) { g_return_if_fail (CB_IS_BUTTON (self)); clutter_actor_set_background_color (self->priv->child, color); } /** * cb_button_set_text_color: * @self: a #CbButton * @color: the #ClutterColor to use as the color for the button text * * Set the color of the text on the button */ void cb_button_set_text_color (CbButton *self, const ClutterColor *color) { g_return_if_fail (CB_IS_BUTTON (self)); clutter_text_set_color (CLUTTER_TEXT (self->priv->label), color); } /** * cb_button_get_text: * @self: a #CbButton * * Get the text displayed on the button * * Returns: the button's text. This must not be freed by the application. */ const gchar * cb_button_get_text (CbButton *self) { g_return_val_if_fail (CB_IS_BUTTON (self), NULL); return self->priv->text; } /** * cb_button_new: * * Creates a new #CbButton instance * * Returns: a new #CbButton */ ClutterActor * cb_button_new (void) { return g_object_new (CB_TYPE_BUTTON, NULL); }