gnome-shell/src/st/st-tooltip.c
Owen W. Taylor a9fd350396 Port our imported parts of Mx to ShellTheme
ShellTheme replaces both StStyle and ccss_stylesheet_t.

The interface StStylable is replaced by usage of ShellThemeNode.
A concrete node class allows some significant optimizations of property
inheritance that would have been much more difficult to achieve with
the highly abstract pair of StStylable and ccss_node_t.

Some operations that were previously on StStylable (like the
::style-changed signal) are directly on NtkWidget.

Custom properties are no longer registered as param-specs; instead you
call directly into shell theme node to look up a length or color:

shell_theme_node_get_length (theme_node, "border-spacing", FALSE, &spacing);

The dependency on libccss is dropped, while preserving all existing
functionality and adding proper parsing and inheritance of font properties
and proper inheritance for the 'color' property.

Some more javascript tests for CSS functionality are added; workarounds for
a CSS bug where *.some-class was needed instead of .some-class are removed.

https://bugzilla.gnome.org/show_bug.cgi?id=595990
2009-10-01 14:41:18 -04:00

691 lines
19 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-tooltip.c: Plain tooltip actor
*
* Copyright 2008, 2009 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
* Boston, MA 02111-1307, USA.
*
* Written by: Thomas Wood <thomas@linux.intel.com>
*
*/
/**
* 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 <stdlib.h>
#include <string.h>
#include <glib.h>
#include <clutter/clutter.h>
#include "st-tooltip.h"
#include "st-widget.h"
#include "st-private.h"
enum
{
PROP_0,
PROP_LABEL,
PROP_TIP_AREA
};
#define ST_TOOLTIP_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TOOLTIP, StTooltipPrivate))
struct _StTooltipPrivate
{
ClutterActor *label;
gfloat arrow_offset;
gboolean actor_below;
ClutterGeometry *tip_area;
};
G_DEFINE_TYPE (StTooltip, st_tooltip, ST_TYPE_WIDGET);
static void
st_tooltip_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
StTooltip *tooltip = ST_TOOLTIP (gobject);
switch (prop_id)
{
case PROP_LABEL:
st_tooltip_set_label (tooltip, g_value_get_string (value));
break;
case PROP_TIP_AREA:
st_tooltip_set_tip_area (tooltip, g_value_get_boxed (value));
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
st_tooltip_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
StTooltipPrivate *priv = ST_TOOLTIP (gobject)->priv;
switch (prop_id)
{
case PROP_LABEL:
g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->label)));
break;
case PROP_TIP_AREA:
g_value_set_boxed (value, priv->tip_area);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
st_tooltip_style_changed (StWidget *self)
{
StTooltipPrivate *priv;
StThemeNode *theme_node;
ClutterColor color;
const PangoFontDescription *font;
gchar *font_string;
priv = ST_TOOLTIP (self)->priv;
theme_node = st_widget_get_theme_node (self);
st_theme_node_get_foreground_color (theme_node, &color);
clutter_text_set_color (CLUTTER_TEXT (priv->label), &color);
font = st_theme_node_get_font (theme_node);
font_string = pango_font_description_to_string (font);
clutter_text_set_font_name (CLUTTER_TEXT (priv->label), font_string);
g_free (font_string);
ST_WIDGET_CLASS (st_tooltip_parent_class)->style_changed (self);
}
static void
st_tooltip_get_preferred_width (ClutterActor *self,
gfloat for_height,
gfloat *min_width_p,
gfloat *natural_width_p)
{
StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
gfloat min_label_w, natural_label_w;
gfloat label_height, arrow_height;
ClutterActor *arrow_image;
StPadding padding;
st_widget_get_padding (ST_WIDGET (self), &padding);
arrow_image = st_widget_get_background_image (ST_WIDGET (self));
if (arrow_image)
{
clutter_actor_get_preferred_height (arrow_image,
-1,
NULL,
&arrow_height);
}
else
{
arrow_height = 0;
}
if (for_height > -1)
{
label_height = for_height - arrow_height - padding.top - padding.bottom;
}
else
{
label_height = -1;
}
if (priv->label)
{
clutter_actor_get_preferred_width (priv->label,
label_height,
&min_label_w,
&natural_label_w);
}
else
{
min_label_w = 0;
natural_label_w = 0;
}
if (min_width_p)
{
*min_width_p = padding.left + padding.right + min_label_w;
}
if (natural_width_p)
{
*natural_width_p = padding.left + padding.right + natural_label_w;
}
}
static void
st_tooltip_get_preferred_height (ClutterActor *self,
gfloat for_width,
gfloat *min_height_p,
gfloat *natural_height_p)
{
StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
gfloat arrow_height;
gfloat min_label_h, natural_label_h;
gfloat label_width;
ClutterActor *arrow_image;
StPadding padding;
arrow_image = st_widget_get_background_image (ST_WIDGET (self));
if (arrow_image && !priv->actor_below)
{
clutter_actor_get_preferred_height (arrow_image,
-1,
NULL,
&arrow_height);
}
else
{
arrow_height = 0;
}
st_widget_get_padding (ST_WIDGET (self), &padding);
if (for_width > -1)
{
label_width = for_width - padding.left - padding.right;
}
else
{
label_width = -1;
}
if (priv->label)
{
clutter_actor_get_preferred_height (priv->label,
label_width,
&min_label_h,
&natural_label_h);
}
else
{
min_label_h = 0;
natural_label_h = 0;
}
if (min_height_p)
{
*min_height_p = padding.top + padding.bottom
+ arrow_height + min_label_h;
}
if (natural_height_p)
{
*natural_height_p = padding.top + padding.bottom
+ arrow_height + natural_label_h;
}
}
static void
st_tooltip_allocate (ClutterActor *self,
const ClutterActorBox *box,
ClutterAllocationFlags flags)
{
StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
ClutterActorBox child_box, arrow_box;
gfloat arrow_height, arrow_width;
ClutterActor *border_image, *arrow_image;
StPadding padding;
CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->allocate (self,
box,
flags);
st_widget_get_padding (ST_WIDGET (self), &padding);
arrow_image = st_widget_get_background_image (ST_WIDGET (self));
if (arrow_image && !priv->actor_below)
{
clutter_actor_get_preferred_height (arrow_image, -1, NULL, &arrow_height);
clutter_actor_get_preferred_width (arrow_image, -1, NULL, &arrow_width);
arrow_box.x1 = (float)(priv->arrow_offset) - (int)(arrow_width / 2);
arrow_box.y1 = 0;
arrow_box.x2 = arrow_box.x1 + arrow_width;
arrow_box.y2 = arrow_box.y1 + arrow_height;
clutter_actor_allocate (arrow_image, &arrow_box, flags);
}
else
{
arrow_height = 0;
arrow_width = 0;
}
child_box.x1 = child_box.y1 = 0;
child_box.x2 = (box->x2 - box->x1);
child_box.y2 = (box->y2 - box->y1);
/* remove the space that is used by the arrow */
child_box.y1 += arrow_height;
border_image = st_widget_get_border_image (ST_WIDGET (self));
if (border_image)
clutter_actor_allocate (border_image, &child_box, flags);
if (priv->label)
{
/* now remove the padding */
child_box.y1 += padding.top;
child_box.x1 += padding.left;
child_box.x2 -= padding.right;
child_box.y2 -= padding.bottom;
clutter_actor_allocate (priv->label, &child_box, flags);
}
}
static void
st_tooltip_paint (ClutterActor *self)
{
ClutterActor *border_image, *arrow_image;
StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
border_image = st_widget_get_border_image (ST_WIDGET (self));
if (border_image)
clutter_actor_paint (border_image);
arrow_image = st_widget_get_background_image (ST_WIDGET (self));
if (arrow_image && !priv->actor_below)
clutter_actor_paint (arrow_image);
clutter_actor_paint (priv->label);
}
static void
st_tooltip_map (ClutterActor *self)
{
StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
ClutterActor *border_image, *arrow_image;
CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->map (self);
border_image = st_widget_get_border_image (ST_WIDGET (self));
if (border_image)
clutter_actor_map (border_image);
arrow_image = st_widget_get_background_image (ST_WIDGET (self));
if (arrow_image)
clutter_actor_map (arrow_image);
clutter_actor_map (priv->label);
}
static void
st_tooltip_unmap (ClutterActor *self)
{
StTooltipPrivate *priv = ST_TOOLTIP (self)->priv;
ClutterActor *border_image, *arrow_image;
CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->unmap (self);
border_image = st_widget_get_border_image (ST_WIDGET (self));
if (border_image)
clutter_actor_unmap (border_image);
arrow_image = st_widget_get_background_image (ST_WIDGET (self));
if (arrow_image)
clutter_actor_unmap (arrow_image);
clutter_actor_unmap (priv->label);
}
static void
st_tooltip_class_init (StTooltipClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
GParamSpec *pspec;
g_type_class_add_private (klass, sizeof (StTooltipPrivate));
gobject_class->set_property = st_tooltip_set_property;
gobject_class->get_property = st_tooltip_get_property;
actor_class->get_preferred_width = st_tooltip_get_preferred_width;
actor_class->get_preferred_height = st_tooltip_get_preferred_height;
actor_class->allocate = st_tooltip_allocate;
actor_class->paint = st_tooltip_paint;
actor_class->map = st_tooltip_map;
actor_class->unmap = st_tooltip_unmap;
widget_class->style_changed = st_tooltip_style_changed;
pspec = g_param_spec_string ("label",
"Label",
"Label of the tooltip",
NULL, G_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_LABEL, pspec);
pspec = g_param_spec_boxed ("tip-area",
"Tip Area",
"Area on the stage the tooltip applies to",
CLUTTER_TYPE_GEOMETRY,
ST_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_TIP_AREA, pspec);
}
static void
st_tooltip_init (StTooltip *tooltip)
{
tooltip->priv = ST_TOOLTIP_GET_PRIVATE (tooltip);
tooltip->priv->label = g_object_new (CLUTTER_TYPE_TEXT,
"line-alignment", PANGO_ALIGN_CENTER,
"ellipsize", PANGO_ELLIPSIZE_END,
"use-markup", TRUE,
NULL);
tooltip->priv->tip_area = NULL;
clutter_actor_set_parent (CLUTTER_ACTOR (tooltip->priv->label),
CLUTTER_ACTOR (tooltip));
g_object_set (tooltip, "show-on-set-parent", FALSE, NULL);
clutter_actor_set_reactive (CLUTTER_ACTOR (tooltip), FALSE);
}
static void
st_tooltip_update_position (StTooltip *tooltip)
{
StTooltipPrivate *priv = tooltip->priv;
ClutterGeometry *tip_area = tooltip->priv->tip_area;
gfloat tooltip_w, tooltip_h, tooltip_x, tooltip_y;
gfloat stage_w, stage_h;
ClutterActor *stage;
/* ensure the tooltip with is not fixed size */
clutter_actor_set_size ((ClutterActor*) tooltip, -1, -1);
/* if no area set, just position ourselves top left */
if (!priv->tip_area)
{
clutter_actor_set_position ((ClutterActor*) tooltip, 0, 0);
return;
}
/* we need to have a style in case there are padding values to take into
* account when calculating width/height */
st_widget_ensure_style ((StWidget *) tooltip);
/* find out the tooltip's size */
clutter_actor_get_size ((ClutterActor*) tooltip, &tooltip_w, &tooltip_h);
/* attempt to place the tooltip */
tooltip_x = (int)(tip_area->x + (tip_area->width / 2) - (tooltip_w / 2));
tooltip_y = (int)(tip_area->y + tip_area->height);
stage = clutter_actor_get_stage ((ClutterActor *) tooltip);
if (!stage)
{
return;
}
clutter_actor_get_size (stage, &stage_w, &stage_h);
/* make sure the tooltip is not off screen vertically */
if (tooltip_w > stage_w)
{
tooltip_x = 0;
clutter_actor_set_width ((ClutterActor*) tooltip, stage_w);
}
else if (tooltip_x < 0)
{
tooltip_x = 0;
}
else if (tooltip_x + tooltip_w > stage_w)
{
tooltip_x = (int)(stage_w) - tooltip_w;
}
/* make sure the tooltip is not off screen horizontally */
if (tooltip_y + tooltip_h > stage_h)
{
priv->actor_below = TRUE;
/* re-query size as may have changed */
clutter_actor_get_preferred_height ((ClutterActor*) tooltip,
-1, NULL, &tooltip_h);
tooltip_y = tip_area->y - tooltip_h;
}
else
{
priv->actor_below = FALSE;
}
/* calculate the arrow offset */
priv->arrow_offset = tip_area->x + tip_area->width / 2 - tooltip_x;
clutter_actor_set_position ((ClutterActor*) tooltip, tooltip_x, tooltip_y);
}
/**
* st_tooltip_get_label:
* @tooltip: a #StTooltip
*
* Get the text displayed on the tooltip
*
* Returns: the text for the tooltip. This must not be freed by the application
*/
G_CONST_RETURN gchar *
st_tooltip_get_label (StTooltip *tooltip)
{
g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL);
return clutter_text_get_text (CLUTTER_TEXT (tooltip->priv->label));
}
/**
* st_tooltip_set_label:
* @tooltip: a #StTooltip
* @text: text to set the label to
*
* Sets the text displayed on the tooltip
*/
void
st_tooltip_set_label (StTooltip *tooltip,
const gchar *text)
{
StTooltipPrivate *priv;
g_return_if_fail (ST_IS_TOOLTIP (tooltip));
priv = tooltip->priv;
clutter_text_set_text (CLUTTER_TEXT (priv->label), text);
g_object_notify (G_OBJECT (tooltip), "label");
}
/**
* st_tooltip_show:
* @tooltip: a #StTooltip
*
* Show the tooltip relative to the associated widget.
*/
void
st_tooltip_show (StTooltip *tooltip)
{
StTooltipPrivate *priv;
ClutterActor *parent;
ClutterActor *stage;
ClutterActor *self = CLUTTER_ACTOR (tooltip);
ClutterAnimation *animation;
/* make sure we're not currently already animating (e.g. hiding) */
animation = clutter_actor_get_animation (CLUTTER_ACTOR (tooltip));
if (animation)
clutter_animation_completed (animation);
priv = tooltip->priv;
parent = clutter_actor_get_parent (self);
stage = clutter_actor_get_stage (self);
if (!stage)
{
g_warning ("StTooltip is not on any stage.");
return;
}
/* make sure we're parented on the stage */
if (G_UNLIKELY (parent != stage))
{
g_object_ref (self);
clutter_actor_unparent (self);
clutter_actor_set_parent (self, stage);
g_object_unref (self);
parent = stage;
}
/* raise the tooltip to the top */
clutter_container_raise_child (CLUTTER_CONTAINER (stage),
CLUTTER_ACTOR (tooltip),
NULL);
st_tooltip_update_position (tooltip);
/* finally show the tooltip... */
CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->show (self);
/* and give it some bounce! */
g_object_set (G_OBJECT (self),
"scale-center-x", priv->arrow_offset,
"scale-center-y", (priv->actor_below) ? clutter_actor_get_height (self) : 0,
NULL);
clutter_actor_set_scale (self, 0.0, 0.0);
clutter_actor_animate (self, CLUTTER_EASE_OUT_ELASTIC,
500,
"scale-x", 1.0,
"scale-y", 1.0,
NULL);
}
static void
st_tooltip_hide_complete (ClutterAnimation *animation,
ClutterActor *actor)
{
CLUTTER_ACTOR_CLASS (st_tooltip_parent_class)->hide (actor);
g_signal_handlers_disconnect_by_func (actor,
st_tooltip_hide_complete,
actor);
}
/**
* st_tooltip_hide:
* @tooltip: a #StTooltip
*
* Hide the tooltip
*/
void
st_tooltip_hide (StTooltip *tooltip)
{
ClutterAnimation *animation;
g_return_if_fail (ST_TOOLTIP (tooltip));
/* make sure we're not currently already animating (e.g. hiding) */
animation = clutter_actor_get_animation (CLUTTER_ACTOR (tooltip));
if (animation)
clutter_animation_completed (animation);
g_object_set (G_OBJECT (tooltip),
"scale-center-x", tooltip->priv->arrow_offset,
NULL);
animation =
clutter_actor_animate (CLUTTER_ACTOR (tooltip), CLUTTER_EASE_IN_SINE,
150,
"scale-x", 0.0,
"scale-y", 0.0,
NULL);
g_signal_connect (animation, "completed",
G_CALLBACK (st_tooltip_hide_complete), tooltip);
}
/**
* st_tooltip_set_tip_area:
* @tooltip: A #StTooltip
* @area: A #ClutterGeometry
*
* Set the area on the stage that the tooltip applies to.
*/
void
st_tooltip_set_tip_area (StTooltip *tooltip,
const ClutterGeometry *area)
{
g_return_if_fail (ST_IS_TOOLTIP (tooltip));
if (tooltip->priv->tip_area)
g_boxed_free (CLUTTER_TYPE_GEOMETRY, tooltip->priv->tip_area);
tooltip->priv->tip_area = g_boxed_copy (CLUTTER_TYPE_GEOMETRY, area);
st_tooltip_update_position (tooltip);
}
/**
* st_tooltip_get_tip_area:
* @tooltip: A #StTooltip
*
* Retrieve the area on the stage that the tooltip currently applies to
*
* Returns: the #ClutterGeometry, owned by the tooltip which must not be freed
* by the application.
*/
G_CONST_RETURN ClutterGeometry*
st_tooltip_get_tip_area (StTooltip *tooltip)
{
g_return_val_if_fail (ST_IS_TOOLTIP (tooltip), NULL);
return tooltip->priv->tip_area;
}