/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * st-tooltip.c: Plain tooltip actor * * Copyright 2008, 2009 Intel Corporation * Copyright 2009 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /** * SECTION:st-tooltip * @short_description: A tooltip widget * * #StTooltip implements a single tooltip. It should not normally be created * by the application but by the widget implementing tooltip capabilities, for * example, #st_button_set_tooltip(). */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include "st-tooltip.h" #include "st-widget.h" #include "st-label.h" #include "st-private.h" enum { PROP_0, PROP_LABEL, PROP_TIP_AREA }; #define ST_TOOLTIP_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TOOLTIP, StTooltipPrivate)) struct _StTooltipPrivate { StLabel *label; ClutterGeometry *tip_area; }; extern gfloat st_slow_down_factor; G_DEFINE_TYPE (StTooltip, st_tooltip, ST_TYPE_WIDGET); static void st_tooltip_show (ClutterActor *self); static void st_tooltip_show_all (ClutterActor *self); static void st_tooltip_hide_all (ClutterActor *self); static void st_tooltip_constrain (StTooltip *tooltip, const ClutterGeometry *geometry, ClutterGeometry *adjusted_geometry); static void st_tooltip_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { StTooltip *tooltip = ST_TOOLTIP (gobject); switch (prop_id) { case PROP_LABEL: st_tooltip_set_label (tooltip, g_value_get_string (value)); break; case PROP_TIP_AREA: st_tooltip_set_tip_area (tooltip, g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void st_tooltip_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { StTooltipPrivate *priv = ST_TOOLTIP (gobject)->priv; switch (prop_id) { case PROP_LABEL: g_value_set_string (value, st_label_get_text (priv->label)); break; case PROP_TIP_AREA: g_value_set_boxed (value, priv->tip_area); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void st_tooltip_get_preferred_width (ClutterActor *self, gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p) { StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self)); gfloat label_height; st_theme_node_adjust_for_height (theme_node, &for_height); if (for_height > -1) { label_height = for_height; } else { label_height = -1; } clutter_actor_get_preferred_width (CLUTTER_ACTOR (priv->label), label_height, min_width_p, natural_width_p); st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p); } static void st_tooltip_get_preferred_height (ClutterActor *self, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self)); gfloat min_label_h, natural_label_h; st_theme_node_adjust_for_width (theme_node, &for_width); clutter_actor_get_preferred_height (CLUTTER_ACTOR (priv->label), for_width, &min_label_h, &natural_label_h); if (min_height_p) *min_height_p = min_label_h; if (natural_height_p) *natural_height_p = natural_label_h; st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p); } static void st_tooltip_allocate (ClutterActor *self, const ClutterActorBox *box, ClutterAllocationFlags flags) { StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self)); ClutterActorBox content_box, child_box; CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->allocate (self, box, flags); st_theme_node_get_content_box (theme_node, box, &content_box); child_box.x1 = child_box.y1 = 0; child_box.x2 = (box->x2 - box->x1); child_box.y2 = (box->y2 - box->y1); child_box = content_box; clutter_actor_allocate (CLUTTER_ACTOR (priv->label), &child_box, flags); } static void st_tooltip_paint (ClutterActor *self) { StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->paint (self); clutter_actor_paint (CLUTTER_ACTOR (priv->label)); } static void st_tooltip_dispose (GObject *self) { StTooltipPrivate *priv = ST_TOOLTIP (self)->priv; if (priv->label) { clutter_actor_destroy (CLUTTER_ACTOR (priv->label)); priv->label = NULL; } G_OBJECT_CLASS (st_tooltip_parent_class)->dispose (self); } static void st_tooltip_class_init (StTooltipClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); GParamSpec *pspec; g_type_class_add_private (klass, sizeof (StTooltipPrivate)); gobject_class->set_property = st_tooltip_set_property; gobject_class->get_property = st_tooltip_get_property; gobject_class->dispose = st_tooltip_dispose; actor_class->get_preferred_width = st_tooltip_get_preferred_width; actor_class->get_preferred_height = st_tooltip_get_preferred_height; actor_class->allocate = st_tooltip_allocate; actor_class->paint = st_tooltip_paint; actor_class->show = st_tooltip_show; actor_class->show_all = st_tooltip_show_all; actor_class->hide_all = st_tooltip_hide_all; pspec = g_param_spec_string ("label", "Label", "Label of the tooltip", NULL, G_PARAM_READWRITE); g_object_class_install_property (gobject_class, PROP_LABEL, pspec); pspec = g_param_spec_boxed ("tip-area", "Tip Area", "Area on the stage the tooltip applies to", CLUTTER_TYPE_GEOMETRY, ST_PARAM_READWRITE); g_object_class_install_property (gobject_class, PROP_TIP_AREA, pspec); } static void st_tooltip_init (StTooltip *tooltip) { tooltip->priv = ST_TOOLTIP_GET_PRIVATE (tooltip); tooltip->priv->label = ST_LABEL (st_label_new (NULL)); tooltip->priv->tip_area = NULL; clutter_actor_set_parent (CLUTTER_ACTOR (tooltip->priv->label), CLUTTER_ACTOR (tooltip)); g_object_set (tooltip, "show-on-set-parent", FALSE, NULL); clutter_actor_set_reactive (CLUTTER_ACTOR (tooltip), FALSE); } static void st_tooltip_update_position (StTooltip *tooltip) { StTooltipPrivate *priv = tooltip->priv; ClutterGeometry *tip_area = tooltip->priv->tip_area; ClutterGeometry geometry; ClutterGeometry adjusted_geometry; gfloat tooltip_w, tooltip_h, tooltip_x, tooltip_y; /* if no area set, just position ourselves top left */ if (!priv->tip_area) { clutter_actor_set_anchor_point ((ClutterActor*) tooltip, 0, 0); return; } /* we need to have a style in case there are padding/border values to take into * account when calculating width/height */ st_widget_ensure_style ((StWidget *) tooltip); /* find out the tooltip's size */ clutter_actor_get_size ((ClutterActor*) tooltip, &tooltip_w, &tooltip_h); /* attempt to place the tooltip */ tooltip_x = (int)(tip_area->x + (tip_area->width / 2) - (tooltip_w / 2)); tooltip_y = (int)(tip_area->y + tip_area->height); geometry.x = tooltip_x; geometry.y = tooltip_y; geometry.width = ceil (tooltip_w); geometry.height = ceil (tooltip_h); st_tooltip_constrain (tooltip, &geometry, &adjusted_geometry); tooltip_x = adjusted_geometry.x; tooltip_y = adjusted_geometry.y; /* Since we are updating the position out of st_widget_allocate(), we can't * call clutter_actor_set_position(), since that would trigger another * allocation cycle. Instead, we adjust the anchor position which moves * the tooltip actor on the screen without changing its allocation */ clutter_actor_set_anchor_point ((ClutterActor*) tooltip, -tooltip_x, -tooltip_y); } /** * st_tooltip_get_label: * @tooltip: a #StTooltip * * Get the text displayed on the tooltip * * Returns: the text for the tooltip. This must not be freed by the application */ const gchar * st_tooltip_get_label (StTooltip *tooltip) { g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL); return st_label_get_text (tooltip->priv->label); } /** * st_tooltip_set_label: * @tooltip: a #StTooltip * @text: text to set the label to * * Sets the text displayed on the tooltip */ void st_tooltip_set_label (StTooltip *tooltip, const gchar *text) { StTooltipPrivate *priv; g_return_if_fail (ST_IS_TOOLTIP (tooltip)); priv = tooltip->priv; st_label_set_text (priv->label, text); g_object_notify (G_OBJECT (tooltip), "label"); } static void st_tooltip_show (ClutterActor *self) { StTooltip *tooltip = ST_TOOLTIP (self); st_tooltip_update_position (tooltip); /* finally show the tooltip... */ CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->show (self); } static void st_tooltip_show_all (ClutterActor *self) { CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->show_all (self); clutter_actor_show_all (CLUTTER_ACTOR (ST_TOOLTIP (self)->priv->label)); } static void st_tooltip_hide_all (ClutterActor *self) { CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->hide_all (self); clutter_actor_hide_all (CLUTTER_ACTOR (ST_TOOLTIP (self)->priv->label)); } /** * st_tooltip_set_tip_area: * @tooltip: A #StTooltip * @area: A #ClutterGeometry * * Set the area on the stage that the tooltip applies to. */ void st_tooltip_set_tip_area (StTooltip *tooltip, const ClutterGeometry *area) { g_return_if_fail (ST_IS_TOOLTIP (tooltip)); if (tooltip->priv->tip_area) g_boxed_free (CLUTTER_TYPE_GEOMETRY, tooltip->priv->tip_area); tooltip->priv->tip_area = g_boxed_copy (CLUTTER_TYPE_GEOMETRY, area); if (clutter_actor_get_stage (CLUTTER_ACTOR (tooltip))) st_tooltip_update_position (tooltip); } /** * st_tooltip_get_tip_area: * @tooltip: A #StTooltip * * Retrieve the area on the stage that the tooltip currently applies to * * Returns: the #ClutterGeometry, owned by the tooltip which must not be freed * by the application. */ const ClutterGeometry* st_tooltip_get_tip_area (StTooltip *tooltip) { g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL); return tooltip->priv->tip_area; } typedef struct { StTooltipConstrainFunc func; gpointer data; GDestroyNotify notify; } ConstrainFuncClosure; static void constrain_func_closure_free (gpointer data) { ConstrainFuncClosure *closure = data; if (closure->notify) closure->notify (closure->data); g_slice_free (ConstrainFuncClosure, data); } static GQuark st_tooltip_constrain_func_quark (void) { static GQuark value = 0; if (G_UNLIKELY (value == 0)) value = g_quark_from_static_string ("st-tooltip-constrain-func"); return value; } /** * st_tooltip_set_constrain_func: * @stage: a #ClutterStage * @func: (allow-none): function to be called to constrain tooltip position * @data: (allow-none): user data to pass to @func * @notify: (allow-none): function to be called when @data is no longer needed * * Sets a callback function that will be used to constrain the position * of tooltips within @stage. This can be used, for example, if the stage * spans multiple monitors and tooltips should be positioned not to cross * monitors. */ void st_tooltip_set_constrain_func (ClutterStage *stage, StTooltipConstrainFunc func, gpointer data, GDestroyNotify notify) { ConstrainFuncClosure *closure; g_return_if_fail (CLUTTER_IS_STAGE (stage)); if (func) { closure = g_slice_new (ConstrainFuncClosure); closure->func = func; closure->data = data; closure->notify = notify; } else closure = NULL; g_object_set_qdata_full (G_OBJECT (stage), st_tooltip_constrain_func_quark (), closure, constrain_func_closure_free); } static void st_tooltip_constrain (StTooltip *tooltip, const ClutterGeometry *geometry, ClutterGeometry *adjusted_geometry) { ConstrainFuncClosure *closure; ClutterActor *stage = clutter_actor_get_stage (CLUTTER_ACTOR (tooltip)); *adjusted_geometry = *geometry; if (stage == NULL) return; closure = g_object_get_qdata (G_OBJECT (stage), st_tooltip_constrain_func_quark ()); if (closure) { closure->func (tooltip, geometry, adjusted_geometry, closure->data); } else { ClutterActor *parent; gfloat parent_w, parent_h; parent = clutter_actor_get_parent ((ClutterActor *) tooltip); clutter_actor_get_size (parent, &parent_w, &parent_h); /* make sure the tooltip is not off parent horizontally */ if (adjusted_geometry->x < 0) adjusted_geometry->x = 0; else if (adjusted_geometry->x + adjusted_geometry->width > parent_w) adjusted_geometry->x = (int)(parent_w) - adjusted_geometry->width; /* make sure the tooltip is not off parent vertically */ if (adjusted_geometry->y + adjusted_geometry->height > parent_h) adjusted_geometry->y = parent_h - adjusted_geometry->height; } }