ea19790828
Since an StWidget now has children, it needs to allocate those children properly. Defer to the currently installed layout manager, like Clutter does. Now that we have something that allocates children in St, to prevent double allocations, we use clutter_actor_set_allocation rather than chaining up to StWidget::allocate. https://bugzilla.gnome.org/show_bug.cgi?id=670034
2465 lines
70 KiB
C
2465 lines
70 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
/*
|
|
* st-widget.c: Base class for St actors
|
|
*
|
|
* Copyright 2007 OpenedHand
|
|
* Copyright 2008, 2009 Intel Corporation.
|
|
* Copyright 2009, 2010 Red Hat, Inc.
|
|
* Copyright 2009 Abderrahim Kitouni
|
|
* Copyright 2009, 2010 Florian Müllner
|
|
* Copyright 2010 Adel Gadllah
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <clutter/clutter.h>
|
|
|
|
#include "st-widget.h"
|
|
|
|
#include "st-label.h"
|
|
#include "st-private.h"
|
|
#include "st-texture-cache.h"
|
|
#include "st-theme-context.h"
|
|
#include "st-theme-node-transition.h"
|
|
|
|
#include "st-widget-accessible.h"
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
/*
|
|
* Forward declaration for sake of StWidgetChild
|
|
*/
|
|
struct _StWidgetPrivate
|
|
{
|
|
StTheme *theme;
|
|
StThemeNode *theme_node;
|
|
gchar *pseudo_class;
|
|
gchar *style_class;
|
|
gchar *inline_style;
|
|
|
|
StThemeNodeTransition *transition_animation;
|
|
|
|
gboolean is_stylable : 1;
|
|
gboolean is_style_dirty : 1;
|
|
gboolean draw_bg_color : 1;
|
|
gboolean draw_border_internal : 1;
|
|
gboolean track_hover : 1;
|
|
gboolean hover : 1;
|
|
gboolean can_focus : 1;
|
|
|
|
StTextDirection direction;
|
|
|
|
AtkObject *accessible;
|
|
|
|
ClutterActor *label_actor;
|
|
|
|
/* Even though Clutter has first_child/last_child properties,
|
|
* we need to keep track of the old first/last children so
|
|
* that we can remove the pseudo classes on them. */
|
|
StWidget *prev_last_child;
|
|
StWidget *prev_first_child;
|
|
};
|
|
|
|
/**
|
|
* SECTION:st-widget
|
|
* @short_description: Base class for stylable actors
|
|
*
|
|
* #StWidget is a simple abstract class on top of #ClutterActor. It
|
|
* provides basic themeing properties.
|
|
*
|
|
* Actors in the St library should subclass #StWidget if they plan
|
|
* to obey to a certain #StStyle.
|
|
*/
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_THEME,
|
|
PROP_PSEUDO_CLASS,
|
|
PROP_STYLE_CLASS,
|
|
PROP_STYLE,
|
|
PROP_STYLABLE,
|
|
PROP_TRACK_HOVER,
|
|
PROP_HOVER,
|
|
PROP_CAN_FOCUS,
|
|
PROP_LABEL_ACTOR
|
|
};
|
|
|
|
enum
|
|
{
|
|
STYLE_CHANGED,
|
|
POPUP_MENU,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0, };
|
|
|
|
gfloat st_slow_down_factor = 1.0;
|
|
|
|
G_DEFINE_TYPE (StWidget, st_widget, CLUTTER_TYPE_ACTOR);
|
|
|
|
#define ST_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_WIDGET, StWidgetPrivate))
|
|
|
|
static void st_widget_recompute_style (StWidget *widget,
|
|
StThemeNode *old_theme_node);
|
|
static gboolean st_widget_real_navigate_focus (StWidget *widget,
|
|
ClutterActor *from,
|
|
GtkDirectionType direction);
|
|
|
|
static AtkObject * st_widget_get_accessible (ClutterActor *actor);
|
|
|
|
static void
|
|
st_widget_set_property (GObject *gobject,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
StWidget *actor = ST_WIDGET (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_THEME:
|
|
st_widget_set_theme (actor, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_PSEUDO_CLASS:
|
|
st_widget_set_style_pseudo_class (actor, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_STYLE_CLASS:
|
|
st_widget_set_style_class_name (actor, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_STYLE:
|
|
st_widget_set_style (actor, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_STYLABLE:
|
|
if (actor->priv->is_stylable != g_value_get_boolean (value))
|
|
{
|
|
actor->priv->is_stylable = g_value_get_boolean (value);
|
|
clutter_actor_queue_relayout ((ClutterActor *) gobject);
|
|
}
|
|
break;
|
|
|
|
case PROP_TRACK_HOVER:
|
|
st_widget_set_track_hover (actor, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_HOVER:
|
|
st_widget_set_hover (actor, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_CAN_FOCUS:
|
|
st_widget_set_can_focus (actor, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_LABEL_ACTOR:
|
|
st_widget_set_label_actor (actor, g_value_get_object (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_widget_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
StWidget *actor = ST_WIDGET (gobject);
|
|
StWidgetPrivate *priv = actor->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_THEME:
|
|
g_value_set_object (value, priv->theme);
|
|
break;
|
|
|
|
case PROP_PSEUDO_CLASS:
|
|
g_value_set_string (value, priv->pseudo_class);
|
|
break;
|
|
|
|
case PROP_STYLE_CLASS:
|
|
g_value_set_string (value, priv->style_class);
|
|
break;
|
|
|
|
case PROP_STYLE:
|
|
g_value_set_string (value, priv->inline_style);
|
|
break;
|
|
|
|
case PROP_STYLABLE:
|
|
g_value_set_boolean (value, priv->is_stylable);
|
|
break;
|
|
|
|
case PROP_TRACK_HOVER:
|
|
g_value_set_boolean (value, priv->track_hover);
|
|
break;
|
|
|
|
case PROP_HOVER:
|
|
g_value_set_boolean (value, priv->hover);
|
|
break;
|
|
|
|
case PROP_CAN_FOCUS:
|
|
g_value_set_boolean (value, priv->can_focus);
|
|
break;
|
|
|
|
case PROP_LABEL_ACTOR:
|
|
g_value_set_object (value, priv->label_actor);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_widget_remove_transition (StWidget *widget)
|
|
{
|
|
if (widget->priv->transition_animation)
|
|
{
|
|
g_object_run_dispose (G_OBJECT (widget->priv->transition_animation));
|
|
g_object_unref (widget->priv->transition_animation);
|
|
widget->priv->transition_animation = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_widget_dispose (GObject *gobject)
|
|
{
|
|
StWidget *actor = ST_WIDGET (gobject);
|
|
StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
|
|
|
|
if (priv->theme)
|
|
{
|
|
g_object_unref (priv->theme);
|
|
priv->theme = NULL;
|
|
}
|
|
|
|
if (priv->theme_node)
|
|
{
|
|
g_object_run_dispose (G_OBJECT (priv->theme_node));
|
|
g_object_unref (priv->theme_node);
|
|
priv->theme_node = NULL;
|
|
}
|
|
|
|
st_widget_remove_transition (actor);
|
|
|
|
/* The real dispose of this accessible is done on
|
|
* AtkGObjectAccessible weak ref callback
|
|
*/
|
|
if (priv->accessible)
|
|
priv->accessible = NULL;
|
|
|
|
if (priv->label_actor)
|
|
{
|
|
g_object_unref (priv->label_actor);
|
|
priv->label_actor = NULL;
|
|
}
|
|
|
|
g_clear_object (&priv->prev_first_child);
|
|
g_clear_object (&priv->prev_last_child);
|
|
|
|
G_OBJECT_CLASS (st_widget_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
st_widget_finalize (GObject *gobject)
|
|
{
|
|
StWidgetPrivate *priv = ST_WIDGET (gobject)->priv;
|
|
|
|
g_free (priv->style_class);
|
|
g_free (priv->pseudo_class);
|
|
|
|
G_OBJECT_CLASS (st_widget_parent_class)->finalize (gobject);
|
|
}
|
|
|
|
|
|
static void
|
|
st_widget_get_preferred_width (ClutterActor *self,
|
|
gfloat for_height,
|
|
gfloat *min_width_p,
|
|
gfloat *natural_width_p)
|
|
{
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
|
|
|
|
st_theme_node_adjust_for_width (theme_node, &for_height);
|
|
|
|
CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_width (self, for_height, min_width_p, natural_width_p);
|
|
|
|
st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
|
|
}
|
|
|
|
static void
|
|
st_widget_get_preferred_height (ClutterActor *self,
|
|
gfloat for_width,
|
|
gfloat *min_height_p,
|
|
gfloat *natural_height_p)
|
|
{
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
|
|
|
|
st_theme_node_adjust_for_width (theme_node, &for_width);
|
|
|
|
CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_height (self, for_width, min_height_p, natural_height_p);
|
|
|
|
st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
|
|
}
|
|
|
|
static void
|
|
st_widget_allocate (ClutterActor *actor,
|
|
const ClutterActorBox *box,
|
|
ClutterAllocationFlags flags)
|
|
{
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
|
|
ClutterActorBox content_box;
|
|
|
|
/* Note that we can't just chain up to clutter_actor_real_allocate --
|
|
* Clutter does some dirty tricks for backwards compatibility.
|
|
* Clutter also passes the actor's allocation directly to the layout
|
|
* manager, meaning that we can't modify it for children only.
|
|
*/
|
|
|
|
clutter_actor_set_allocation (actor, box, flags);
|
|
|
|
st_theme_node_get_content_box (theme_node, box, &content_box);
|
|
|
|
/* If we've chained up to here, we want to allocate the children using the
|
|
* currently installed layout manager */
|
|
clutter_layout_manager_allocate (clutter_actor_get_layout_manager (actor),
|
|
CLUTTER_CONTAINER (actor),
|
|
&content_box,
|
|
flags);
|
|
}
|
|
|
|
/**
|
|
* st_widget_paint_background:
|
|
* @widget: The #StWidget
|
|
*
|
|
* Paint the background of the widget. This is meant to be called by
|
|
* subclasses of StWiget that need to paint the background without
|
|
* painting children.
|
|
*/
|
|
void
|
|
st_widget_paint_background (StWidget *widget)
|
|
{
|
|
StThemeNode *theme_node;
|
|
ClutterActorBox allocation;
|
|
guint8 opacity;
|
|
|
|
theme_node = st_widget_get_theme_node (widget);
|
|
|
|
clutter_actor_get_allocation_box (CLUTTER_ACTOR (widget), &allocation);
|
|
|
|
opacity = clutter_actor_get_paint_opacity (CLUTTER_ACTOR (widget));
|
|
|
|
if (widget->priv->transition_animation)
|
|
st_theme_node_transition_paint (widget->priv->transition_animation,
|
|
&allocation,
|
|
opacity);
|
|
else
|
|
st_theme_node_paint (theme_node, &allocation, opacity);
|
|
}
|
|
|
|
static void
|
|
st_widget_paint (ClutterActor *actor)
|
|
{
|
|
st_widget_paint_background (ST_WIDGET (actor));
|
|
|
|
/* Chain up so we paint children. */
|
|
CLUTTER_ACTOR_CLASS (st_widget_parent_class)->paint (actor);
|
|
}
|
|
|
|
static void
|
|
st_widget_parent_set (ClutterActor *widget,
|
|
ClutterActor *old_parent)
|
|
{
|
|
ClutterActorClass *parent_class;
|
|
ClutterActor *new_parent;
|
|
|
|
parent_class = CLUTTER_ACTOR_CLASS (st_widget_parent_class);
|
|
if (parent_class->parent_set)
|
|
parent_class->parent_set (widget, old_parent);
|
|
|
|
new_parent = clutter_actor_get_parent (widget);
|
|
|
|
/* don't send the style changed signal if we no longer have a parent actor */
|
|
if (new_parent)
|
|
st_widget_style_changed (ST_WIDGET (widget));
|
|
}
|
|
|
|
static void
|
|
st_widget_map (ClutterActor *actor)
|
|
{
|
|
StWidget *self = ST_WIDGET (actor);
|
|
|
|
CLUTTER_ACTOR_CLASS (st_widget_parent_class)->map (actor);
|
|
|
|
st_widget_ensure_style (self);
|
|
}
|
|
|
|
static void
|
|
st_widget_unmap (ClutterActor *actor)
|
|
{
|
|
StWidget *self = ST_WIDGET (actor);
|
|
StWidgetPrivate *priv = self->priv;
|
|
|
|
CLUTTER_ACTOR_CLASS (st_widget_parent_class)->unmap (actor);
|
|
|
|
if (priv->track_hover && priv->hover)
|
|
st_widget_set_hover (self, FALSE);
|
|
}
|
|
|
|
static void
|
|
notify_children_of_style_change (ClutterActor *self)
|
|
{
|
|
ClutterActorIter iter;
|
|
ClutterActor *actor;
|
|
|
|
clutter_actor_iter_init (&iter, self);
|
|
while (clutter_actor_iter_next (&iter, &actor))
|
|
{
|
|
if (ST_IS_WIDGET (actor))
|
|
st_widget_style_changed (ST_WIDGET (actor));
|
|
else
|
|
notify_children_of_style_change (actor);
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_widget_real_style_changed (StWidget *self)
|
|
{
|
|
StWidgetPrivate *priv = ST_WIDGET (self)->priv;
|
|
|
|
/* application has request this widget is not stylable */
|
|
if (!priv->is_stylable)
|
|
return;
|
|
|
|
clutter_actor_queue_redraw ((ClutterActor *) self);
|
|
notify_children_of_style_change ((ClutterActor *) self);
|
|
}
|
|
|
|
void
|
|
st_widget_style_changed (StWidget *widget)
|
|
{
|
|
StThemeNode *old_theme_node = NULL;
|
|
|
|
widget->priv->is_style_dirty = TRUE;
|
|
if (widget->priv->theme_node)
|
|
{
|
|
old_theme_node = widget->priv->theme_node;
|
|
widget->priv->theme_node = NULL;
|
|
}
|
|
|
|
/* update the style only if we are mapped */
|
|
if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR (widget)))
|
|
st_widget_recompute_style (widget, old_theme_node);
|
|
|
|
if (old_theme_node)
|
|
g_object_unref (old_theme_node);
|
|
}
|
|
|
|
static void
|
|
on_theme_context_changed (StThemeContext *context,
|
|
ClutterStage *stage)
|
|
{
|
|
notify_children_of_style_change (CLUTTER_ACTOR (stage));
|
|
}
|
|
|
|
static StThemeNode *
|
|
get_root_theme_node (ClutterStage *stage)
|
|
{
|
|
StThemeContext *context = st_theme_context_get_for_stage (stage);
|
|
|
|
if (!g_object_get_data (G_OBJECT (context), "st-theme-initialized"))
|
|
{
|
|
g_object_set_data (G_OBJECT (context), "st-theme-initialized", GUINT_TO_POINTER (1));
|
|
g_signal_connect (G_OBJECT (context), "changed",
|
|
G_CALLBACK (on_theme_context_changed), stage);
|
|
}
|
|
|
|
return st_theme_context_get_root_node (context);
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_theme_node:
|
|
* @widget: a #StWidget
|
|
*
|
|
* Gets the theme node holding style information for the widget.
|
|
* The theme node is used to access standard and custom CSS
|
|
* properties of the widget.
|
|
*
|
|
* Note: it is a fatal error to call this on a widget that is
|
|
* not been added to a stage.
|
|
*
|
|
* Return value: (transfer none): the theme node for the widget.
|
|
* This is owned by the widget. When attributes of the widget
|
|
* or the environment that affect the styling change (for example
|
|
* the style_class property of the widget), it will be recreated,
|
|
* and the ::style-changed signal will be emitted on the widget.
|
|
*/
|
|
StThemeNode *
|
|
st_widget_get_theme_node (StWidget *widget)
|
|
{
|
|
StWidgetPrivate *priv = widget->priv;
|
|
|
|
if (priv->theme_node == NULL)
|
|
{
|
|
StThemeNode *parent_node = NULL;
|
|
ClutterStage *stage = NULL;
|
|
ClutterActor *parent;
|
|
char *pseudo_class, *direction_pseudo_class;
|
|
|
|
parent = clutter_actor_get_parent (CLUTTER_ACTOR (widget));
|
|
while (parent != NULL)
|
|
{
|
|
if (parent_node == NULL && ST_IS_WIDGET (parent))
|
|
parent_node = st_widget_get_theme_node (ST_WIDGET (parent));
|
|
else if (CLUTTER_IS_STAGE (parent))
|
|
stage = CLUTTER_STAGE (parent);
|
|
|
|
parent = clutter_actor_get_parent (parent);
|
|
}
|
|
|
|
if (stage == NULL)
|
|
{
|
|
g_error ("st_widget_get_theme_node called on the widget %s which is not in the stage.",
|
|
st_describe_actor (CLUTTER_ACTOR (widget)));
|
|
}
|
|
|
|
if (parent_node == NULL)
|
|
parent_node = get_root_theme_node (CLUTTER_STAGE (stage));
|
|
|
|
/* Always append a "magic" pseudo class indicating the text
|
|
* direction, to allow to adapt the CSS when necessary without
|
|
* requiring separate style sheets.
|
|
*/
|
|
if (st_widget_get_direction (widget) == ST_TEXT_DIRECTION_RTL)
|
|
direction_pseudo_class = "rtl";
|
|
else
|
|
direction_pseudo_class = "ltr";
|
|
|
|
if (priv->pseudo_class)
|
|
pseudo_class = g_strconcat(priv->pseudo_class, " ",
|
|
direction_pseudo_class, NULL);
|
|
else
|
|
pseudo_class = direction_pseudo_class;
|
|
|
|
priv->theme_node = st_theme_node_new (st_theme_context_get_for_stage (stage),
|
|
parent_node, priv->theme,
|
|
G_OBJECT_TYPE (widget),
|
|
clutter_actor_get_name (CLUTTER_ACTOR (widget)),
|
|
priv->style_class,
|
|
pseudo_class,
|
|
priv->inline_style);
|
|
|
|
if (pseudo_class != direction_pseudo_class)
|
|
g_free (pseudo_class);
|
|
}
|
|
|
|
return priv->theme_node;
|
|
}
|
|
|
|
/**
|
|
* st_widget_peek_theme_node:
|
|
* @widget: a #StWidget
|
|
*
|
|
* Returns the theme node for the widget if it has already been
|
|
* computed, %NULL if the widget hasn't been added to a stage or the theme
|
|
* node hasn't been computed. If %NULL is returned, then ::style-changed
|
|
* will be reliably emitted before the widget is allocated or painted.
|
|
*
|
|
* Return value: (transfer none): the theme node for the widget.
|
|
* This is owned by the widget. When attributes of the widget
|
|
* or the environment that affect the styling change (for example
|
|
* the style_class property of the widget), it will be recreated,
|
|
* and the ::style-changed signal will be emitted on the widget.
|
|
*/
|
|
StThemeNode *
|
|
st_widget_peek_theme_node (StWidget *widget)
|
|
{
|
|
StWidgetPrivate *priv = widget->priv;
|
|
|
|
return priv->theme_node;
|
|
}
|
|
|
|
static gboolean
|
|
st_widget_enter (ClutterActor *actor,
|
|
ClutterCrossingEvent *event)
|
|
{
|
|
StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
|
|
|
|
if (priv->track_hover)
|
|
{
|
|
if (clutter_actor_contains (actor, event->source))
|
|
st_widget_set_hover (ST_WIDGET (actor), TRUE);
|
|
else
|
|
{
|
|
/* The widget has a grab and is being told about an
|
|
* enter-event outside its hierarchy. Hopefully we already
|
|
* got a leave-event, but if not, handle it now.
|
|
*/
|
|
st_widget_set_hover (ST_WIDGET (actor), FALSE);
|
|
}
|
|
}
|
|
|
|
if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event)
|
|
return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event (actor, event);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
st_widget_leave (ClutterActor *actor,
|
|
ClutterCrossingEvent *event)
|
|
{
|
|
StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
|
|
|
|
if (priv->track_hover)
|
|
{
|
|
if (!event->related || !clutter_actor_contains (actor, event->related))
|
|
st_widget_set_hover (ST_WIDGET (actor), FALSE);
|
|
}
|
|
|
|
if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event)
|
|
return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event (actor, event);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
st_widget_key_focus_in (ClutterActor *actor)
|
|
{
|
|
StWidget *widget = ST_WIDGET (actor);
|
|
|
|
st_widget_add_style_pseudo_class (widget, "focus");
|
|
}
|
|
|
|
static void
|
|
st_widget_key_focus_out (ClutterActor *actor)
|
|
{
|
|
StWidget *widget = ST_WIDGET (actor);
|
|
|
|
st_widget_remove_style_pseudo_class (widget, "focus");
|
|
}
|
|
|
|
static gboolean
|
|
st_widget_key_press_event (ClutterActor *actor,
|
|
ClutterKeyEvent *event)
|
|
{
|
|
if (event->keyval == CLUTTER_KEY_Menu ||
|
|
(event->keyval == CLUTTER_KEY_F10 &&
|
|
(event->modifier_state & CLUTTER_SHIFT_MASK)))
|
|
{
|
|
g_signal_emit (actor, signals[POPUP_MENU], 0);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
st_widget_get_paint_volume (ClutterActor *self,
|
|
ClutterPaintVolume *volume)
|
|
{
|
|
ClutterActorBox paint_box, alloc_box;
|
|
StThemeNode *theme_node;
|
|
StWidgetPrivate *priv;
|
|
ClutterVertex origin;
|
|
|
|
/* Setting the paint volume does not make sense when we don't have any allocation */
|
|
if (!clutter_actor_has_allocation (self))
|
|
return FALSE;
|
|
|
|
priv = ST_WIDGET (self)->priv;
|
|
|
|
theme_node = st_widget_get_theme_node (ST_WIDGET (self));
|
|
clutter_actor_get_allocation_box (self, &alloc_box);
|
|
|
|
if (priv->transition_animation)
|
|
st_theme_node_transition_get_paint_box (priv->transition_animation,
|
|
&alloc_box, &paint_box);
|
|
else
|
|
st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box);
|
|
|
|
origin.x = paint_box.x1 - alloc_box.x1;
|
|
origin.y = paint_box.y1 - alloc_box.y1;
|
|
origin.z = 0.0f;
|
|
|
|
clutter_paint_volume_set_origin (volume, &origin);
|
|
clutter_paint_volume_set_width (volume, paint_box.x2 - paint_box.x1);
|
|
clutter_paint_volume_set_height (volume, paint_box.y2 - paint_box.y1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GList *
|
|
st_widget_real_get_focus_chain (StWidget *widget)
|
|
{
|
|
return clutter_actor_get_children (CLUTTER_ACTOR (widget));
|
|
}
|
|
|
|
|
|
static void
|
|
st_widget_class_init (StWidgetClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
GParamSpec *pspec;
|
|
|
|
g_type_class_add_private (klass, sizeof (StWidgetPrivate));
|
|
|
|
gobject_class->set_property = st_widget_set_property;
|
|
gobject_class->get_property = st_widget_get_property;
|
|
gobject_class->dispose = st_widget_dispose;
|
|
gobject_class->finalize = st_widget_finalize;
|
|
|
|
actor_class->get_preferred_width = st_widget_get_preferred_width;
|
|
actor_class->get_preferred_height = st_widget_get_preferred_height;
|
|
actor_class->allocate = st_widget_allocate;
|
|
actor_class->paint = st_widget_paint;
|
|
actor_class->get_paint_volume = st_widget_get_paint_volume;
|
|
actor_class->parent_set = st_widget_parent_set;
|
|
actor_class->map = st_widget_map;
|
|
actor_class->unmap = st_widget_unmap;
|
|
|
|
actor_class->enter_event = st_widget_enter;
|
|
actor_class->leave_event = st_widget_leave;
|
|
actor_class->key_focus_in = st_widget_key_focus_in;
|
|
actor_class->key_focus_out = st_widget_key_focus_out;
|
|
actor_class->key_press_event = st_widget_key_press_event;
|
|
|
|
actor_class->get_accessible = st_widget_get_accessible;
|
|
|
|
klass->style_changed = st_widget_real_style_changed;
|
|
klass->navigate_focus = st_widget_real_navigate_focus;
|
|
klass->get_accessible_type = st_widget_accessible_get_type;
|
|
klass->get_focus_chain = st_widget_real_get_focus_chain;
|
|
|
|
/**
|
|
* StWidget:pseudo-class:
|
|
*
|
|
* The pseudo-class of the actor. Typical values include "hover", "active",
|
|
* "focus".
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_PSEUDO_CLASS,
|
|
g_param_spec_string ("pseudo-class",
|
|
"Pseudo Class",
|
|
"Pseudo class for styling",
|
|
"",
|
|
ST_PARAM_READWRITE));
|
|
/**
|
|
* StWidget:style-class:
|
|
*
|
|
* The style-class of the actor for use in styling.
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_STYLE_CLASS,
|
|
g_param_spec_string ("style-class",
|
|
"Style Class",
|
|
"Style class for styling",
|
|
"",
|
|
ST_PARAM_READWRITE));
|
|
|
|
/**
|
|
* StWidget:style:
|
|
*
|
|
* Inline style information for the actor as a ';'-separated list of
|
|
* CSS properties.
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_STYLE,
|
|
g_param_spec_string ("style",
|
|
"Style",
|
|
"Inline style string",
|
|
"",
|
|
ST_PARAM_READWRITE));
|
|
|
|
/**
|
|
* StWidget:theme
|
|
*
|
|
* A theme set on this actor overriding the global theming for this actor
|
|
* and its descendants
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_THEME,
|
|
g_param_spec_object ("theme",
|
|
"Theme",
|
|
"Theme override",
|
|
ST_TYPE_THEME,
|
|
ST_PARAM_READWRITE));
|
|
|
|
/**
|
|
* StWidget:stylable:
|
|
*
|
|
* Enable or disable styling of the widget
|
|
*/
|
|
pspec = g_param_spec_boolean ("stylable",
|
|
"Stylable",
|
|
"Whether the table should be styled",
|
|
TRUE,
|
|
ST_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_STYLABLE,
|
|
pspec);
|
|
|
|
/**
|
|
* StWidget:track-hover:
|
|
*
|
|
* Determines whether the widget tracks pointer hover state. If
|
|
* %TRUE (and the widget is visible and reactive), the
|
|
* #StWidget:hover property and "hover" style pseudo class will be
|
|
* adjusted automatically as the pointer moves in and out of the
|
|
* widget.
|
|
*/
|
|
pspec = g_param_spec_boolean ("track-hover",
|
|
"Track hover",
|
|
"Determines whether the widget tracks hover state",
|
|
FALSE,
|
|
ST_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_TRACK_HOVER,
|
|
pspec);
|
|
|
|
/**
|
|
* StWidget:hover:
|
|
*
|
|
* Whether or not the pointer is currently hovering over the widget. This is
|
|
* only tracked automatically if #StWidget:track-hover is %TRUE, but you can
|
|
* adjust it manually in any case.
|
|
*/
|
|
pspec = g_param_spec_boolean ("hover",
|
|
"Hover",
|
|
"Whether the pointer is hovering over the widget",
|
|
FALSE,
|
|
ST_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_HOVER,
|
|
pspec);
|
|
|
|
/**
|
|
* StWidget:can-focus:
|
|
*
|
|
* Whether or not the widget can be focused via keyboard navigation.
|
|
*/
|
|
pspec = g_param_spec_boolean ("can-focus",
|
|
"Can focus",
|
|
"Whether the widget can be focused via keyboard navigation",
|
|
FALSE,
|
|
ST_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CAN_FOCUS,
|
|
pspec);
|
|
|
|
/**
|
|
* ClutterActor:label-actor:
|
|
*
|
|
* An actor that labels this widget.
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_LABEL_ACTOR,
|
|
g_param_spec_object ("label-actor",
|
|
"Label",
|
|
"Label that identifies this widget",
|
|
CLUTTER_TYPE_ACTOR,
|
|
ST_PARAM_READWRITE));
|
|
|
|
/**
|
|
* StWidget::style-changed:
|
|
* @widget: the #StWidget
|
|
*
|
|
* Emitted when the style information that the widget derives from the
|
|
* theme changes
|
|
*/
|
|
signals[STYLE_CHANGED] =
|
|
g_signal_new ("style-changed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (StWidgetClass, style_changed),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
/**
|
|
* StWidget::popup-menu:
|
|
* @widget: the #StWidget
|
|
*
|
|
* Emitted when the user has requested a context menu (eg, via a
|
|
* keybinding)
|
|
*/
|
|
signals[POPUP_MENU] =
|
|
g_signal_new ("popup-menu",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (StWidgetClass, popup_menu),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_theme:
|
|
* @actor: a #StWidget
|
|
* @theme: a new style class string
|
|
*
|
|
* Overrides the theme that would be inherited from the actor's parent
|
|
* or the stage with an entirely new theme (set of stylesheets).
|
|
*/
|
|
void
|
|
st_widget_set_theme (StWidget *actor,
|
|
StTheme *theme)
|
|
{
|
|
StWidgetPrivate *priv = actor->priv;
|
|
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
|
|
priv = actor->priv;
|
|
|
|
if (theme !=priv->theme)
|
|
{
|
|
if (priv->theme)
|
|
g_object_unref (priv->theme);
|
|
priv->theme = g_object_ref (priv->theme);
|
|
|
|
st_widget_style_changed (actor);
|
|
|
|
g_object_notify (G_OBJECT (actor), "theme");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_theme:
|
|
* @actor: a #StWidget
|
|
*
|
|
* Gets the overriding theme set on the actor. See st_widget_set_theme()
|
|
*
|
|
* Return value: (transfer none): the overriding theme, or %NULL
|
|
*/
|
|
StTheme *
|
|
st_widget_get_theme (StWidget *actor)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
|
|
|
|
return actor->priv->theme;
|
|
}
|
|
|
|
static const gchar *
|
|
find_class_name (const gchar *class_list,
|
|
const gchar *class_name)
|
|
{
|
|
gint len = strlen (class_name);
|
|
const gchar *match;
|
|
|
|
if (!class_list)
|
|
return NULL;
|
|
|
|
for (match = strstr (class_list, class_name); match; match = strstr (match + 1, class_name))
|
|
{
|
|
if ((match == class_list || g_ascii_isspace (match[-1])) &&
|
|
(match[len] == '\0' || g_ascii_isspace (match[len])))
|
|
return match;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
set_class_list (gchar **class_list,
|
|
const gchar *new_class_list)
|
|
{
|
|
if (g_strcmp0 (*class_list, new_class_list) != 0)
|
|
{
|
|
g_free (*class_list);
|
|
*class_list = g_strdup (new_class_list);
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
add_class_name (gchar **class_list,
|
|
const gchar *class_name)
|
|
{
|
|
gchar *new_class_list;
|
|
|
|
if (*class_list)
|
|
{
|
|
if (find_class_name (*class_list, class_name))
|
|
return FALSE;
|
|
|
|
new_class_list = g_strdup_printf ("%s %s", *class_list, class_name);
|
|
g_free (*class_list);
|
|
*class_list = new_class_list;
|
|
}
|
|
else
|
|
*class_list = g_strdup (class_name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
remove_class_name (gchar **class_list,
|
|
const gchar *class_name)
|
|
{
|
|
const gchar *match, *end;
|
|
gchar *new_class_list;
|
|
|
|
if (!*class_list)
|
|
return FALSE;
|
|
|
|
if (strcmp (*class_list, class_name) == 0)
|
|
{
|
|
g_free (*class_list);
|
|
*class_list = NULL;
|
|
return TRUE;
|
|
}
|
|
|
|
match = find_class_name (*class_list, class_name);
|
|
if (!match)
|
|
return FALSE;
|
|
end = match + strlen (class_name);
|
|
|
|
/* Adjust either match or end to include a space as well.
|
|
* (One or the other must be possible at this point.)
|
|
*/
|
|
if (match != *class_list)
|
|
match--;
|
|
else
|
|
end++;
|
|
|
|
new_class_list = g_strdup_printf ("%.*s%s", (int)(match - *class_list),
|
|
*class_list, end);
|
|
g_free (*class_list);
|
|
*class_list = new_class_list;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_style_class_name:
|
|
* @actor: a #StWidget
|
|
* @style_class_list: (allow-none): a new style class list string
|
|
*
|
|
* Set the style class name list. @style_class_list can either be
|
|
* %NULL, for no classes, or a space-separated list of style class
|
|
* names. See also st_widget_add_style_class_name() and
|
|
* st_widget_remove_style_class_name().
|
|
*/
|
|
void
|
|
st_widget_set_style_class_name (StWidget *actor,
|
|
const gchar *style_class_list)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
|
|
if (set_class_list (&actor->priv->style_class, style_class_list))
|
|
{
|
|
st_widget_style_changed (actor);
|
|
g_object_notify (G_OBJECT (actor), "style-class");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_add_style_class_name:
|
|
* @actor: a #StWidget
|
|
* @style_class: a style class name string
|
|
*
|
|
* Adds @style_class to @actor's style class name list, if it is not
|
|
* already present.
|
|
*/
|
|
void
|
|
st_widget_add_style_class_name (StWidget *actor,
|
|
const gchar *style_class)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
g_return_if_fail (style_class != NULL);
|
|
|
|
if (add_class_name (&actor->priv->style_class, style_class))
|
|
{
|
|
st_widget_style_changed (actor);
|
|
g_object_notify (G_OBJECT (actor), "style-class");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_remove_style_class_name:
|
|
* @actor: a #StWidget
|
|
* @style_class: a style class name string
|
|
*
|
|
* Removes @style_class from @actor's style class name, if it is
|
|
* present.
|
|
*/
|
|
void
|
|
st_widget_remove_style_class_name (StWidget *actor,
|
|
const gchar *style_class)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
g_return_if_fail (style_class != NULL);
|
|
|
|
if (remove_class_name (&actor->priv->style_class, style_class))
|
|
{
|
|
st_widget_style_changed (actor);
|
|
g_object_notify (G_OBJECT (actor), "style-class");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_style_class_name:
|
|
* @actor: a #StWidget
|
|
*
|
|
* Get the current style class name
|
|
*
|
|
* Returns: the class name string. The string is owned by the #StWidget and
|
|
* should not be modified or freed.
|
|
*/
|
|
const gchar*
|
|
st_widget_get_style_class_name (StWidget *actor)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
|
|
|
|
return actor->priv->style_class;
|
|
}
|
|
|
|
/**
|
|
* st_widget_has_style_class_name:
|
|
* @actor: a #StWidget
|
|
* @style_class: a style class string
|
|
*
|
|
* Tests if @actor's style class list includes @style_class.
|
|
*
|
|
* Returns: whether or not @actor's style class list includes
|
|
* @style_class.
|
|
*/
|
|
gboolean
|
|
st_widget_has_style_class_name (StWidget *actor,
|
|
const gchar *style_class)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
|
|
|
|
return find_class_name (actor->priv->style_class, style_class) != NULL;
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_style_pseudo_class:
|
|
* @actor: a #StWidget
|
|
*
|
|
* Get the current style pseudo class list.
|
|
*
|
|
* Note that an actor can have multiple pseudo classes; if you just
|
|
* want to test for the presence of a specific pseudo class, use
|
|
* st_widget_has_style_pseudo_class().
|
|
*
|
|
* Returns: the pseudo class list string. The string is owned by the
|
|
* #StWidget and should not be modified or freed.
|
|
*/
|
|
const gchar*
|
|
st_widget_get_style_pseudo_class (StWidget *actor)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
|
|
|
|
return actor->priv->pseudo_class;
|
|
}
|
|
|
|
/**
|
|
* st_widget_has_style_pseudo_class:
|
|
* @actor: a #StWidget
|
|
* @pseudo_class: a pseudo class string
|
|
*
|
|
* Tests if @actor's pseudo class list includes @pseudo_class.
|
|
*
|
|
* Returns: whether or not @actor's pseudo class list includes
|
|
* @pseudo_class.
|
|
*/
|
|
gboolean
|
|
st_widget_has_style_pseudo_class (StWidget *actor,
|
|
const gchar *pseudo_class)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
|
|
|
|
return find_class_name (actor->priv->pseudo_class, pseudo_class) != NULL;
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_style_pseudo_class:
|
|
* @actor: a #StWidget
|
|
* @pseudo_class_list: (allow-none): a new pseudo class list string
|
|
*
|
|
* Set the style pseudo class list. @pseudo_class_list can either be
|
|
* %NULL, for no classes, or a space-separated list of pseudo class
|
|
* names. See also st_widget_add_style_pseudo_class() and
|
|
* st_widget_remove_style_pseudo_class().
|
|
*/
|
|
void
|
|
st_widget_set_style_pseudo_class (StWidget *actor,
|
|
const gchar *pseudo_class_list)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
|
|
if (set_class_list (&actor->priv->pseudo_class, pseudo_class_list))
|
|
{
|
|
st_widget_style_changed (actor);
|
|
g_object_notify (G_OBJECT (actor), "pseudo-class");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_add_style_pseudo_class:
|
|
* @actor: a #StWidget
|
|
* @pseudo_class: a pseudo class string
|
|
*
|
|
* Adds @pseudo_class to @actor's pseudo class list, if it is not
|
|
* already present.
|
|
*/
|
|
void
|
|
st_widget_add_style_pseudo_class (StWidget *actor,
|
|
const gchar *pseudo_class)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
g_return_if_fail (pseudo_class != NULL);
|
|
|
|
if (add_class_name (&actor->priv->pseudo_class, pseudo_class))
|
|
{
|
|
st_widget_style_changed (actor);
|
|
g_object_notify (G_OBJECT (actor), "pseudo-class");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_remove_style_pseudo_class:
|
|
* @actor: a #StWidget
|
|
* @pseudo_class: a pseudo class string
|
|
*
|
|
* Removes @pseudo_class from @actor's pseudo class, if it is present.
|
|
*/
|
|
void
|
|
st_widget_remove_style_pseudo_class (StWidget *actor,
|
|
const gchar *pseudo_class)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
g_return_if_fail (pseudo_class != NULL);
|
|
|
|
if (remove_class_name (&actor->priv->pseudo_class, pseudo_class))
|
|
{
|
|
st_widget_style_changed (actor);
|
|
g_object_notify (G_OBJECT (actor), "pseudo-class");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_style:
|
|
* @actor: a #StWidget
|
|
* @style: (allow-none): a inline style string, or %NULL
|
|
*
|
|
* Set the inline style string for this widget. The inline style string is an
|
|
* optional ';'-separated list of CSS properties that override the style as
|
|
* determined from the stylesheets of the current theme.
|
|
*/
|
|
void
|
|
st_widget_set_style (StWidget *actor,
|
|
const gchar *style)
|
|
{
|
|
StWidgetPrivate *priv = actor->priv;
|
|
|
|
g_return_if_fail (ST_IS_WIDGET (actor));
|
|
|
|
priv = actor->priv;
|
|
|
|
if (g_strcmp0 (style, priv->inline_style))
|
|
{
|
|
g_free (priv->inline_style);
|
|
priv->inline_style = g_strdup (style);
|
|
|
|
st_widget_style_changed (actor);
|
|
|
|
g_object_notify (G_OBJECT (actor), "style");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_style:
|
|
* @actor: a #StWidget
|
|
*
|
|
* Get the current inline style string. See st_widget_set_style().
|
|
*
|
|
* Returns: The inline style string, or %NULL. The string is owned by the
|
|
* #StWidget and should not be modified or freed.
|
|
*/
|
|
const gchar*
|
|
st_widget_get_style (StWidget *actor)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
|
|
|
|
return actor->priv->inline_style;
|
|
}
|
|
|
|
static void
|
|
st_widget_name_notify (StWidget *widget,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
st_widget_style_changed (widget);
|
|
}
|
|
|
|
static void
|
|
st_widget_first_child_notify (StWidget *widget,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
ClutterActor *first_child;
|
|
|
|
if (widget->priv->prev_first_child != NULL)
|
|
{
|
|
st_widget_remove_style_pseudo_class (widget->priv->prev_first_child, "first-child");
|
|
g_clear_object (&widget->priv->prev_first_child);
|
|
}
|
|
|
|
first_child = clutter_actor_get_first_child (CLUTTER_ACTOR (widget));
|
|
|
|
if (first_child == NULL)
|
|
return;
|
|
|
|
if (ST_IS_WIDGET (first_child))
|
|
{
|
|
st_widget_add_style_pseudo_class (ST_WIDGET (first_child), "first-child");
|
|
widget->priv->prev_first_child = g_object_ref (ST_WIDGET (first_child));
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_widget_last_child_notify (StWidget *widget,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
ClutterActor *last_child;
|
|
|
|
if (widget->priv->prev_last_child != NULL)
|
|
{
|
|
st_widget_remove_style_pseudo_class (widget->priv->prev_last_child, "last-child");
|
|
g_clear_object (&widget->priv->prev_last_child);
|
|
}
|
|
|
|
last_child = clutter_actor_get_last_child (CLUTTER_ACTOR (widget));
|
|
|
|
if (last_child == NULL)
|
|
return;
|
|
|
|
if (ST_IS_WIDGET (last_child))
|
|
{
|
|
st_widget_add_style_pseudo_class (ST_WIDGET (last_child), "last-child");
|
|
widget->priv->prev_last_child = g_object_ref (ST_WIDGET (last_child));
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_widget_init (StWidget *actor)
|
|
{
|
|
StWidgetPrivate *priv;
|
|
|
|
actor->priv = priv = ST_WIDGET_GET_PRIVATE (actor);
|
|
priv->is_stylable = TRUE;
|
|
priv->transition_animation = NULL;
|
|
|
|
/* connect style changed */
|
|
g_signal_connect (actor, "notify::name", G_CALLBACK (st_widget_name_notify), NULL);
|
|
|
|
g_signal_connect (actor, "notify::first-child", G_CALLBACK (st_widget_first_child_notify), NULL);
|
|
g_signal_connect (actor, "notify::last-child", G_CALLBACK (st_widget_last_child_notify), NULL);
|
|
}
|
|
|
|
static void
|
|
on_transition_completed (StThemeNodeTransition *transition,
|
|
StWidget *widget)
|
|
{
|
|
st_widget_remove_transition (widget);
|
|
}
|
|
|
|
static void
|
|
st_widget_recompute_style (StWidget *widget,
|
|
StThemeNode *old_theme_node)
|
|
{
|
|
StThemeNode *new_theme_node = st_widget_get_theme_node (widget);
|
|
int transition_duration;
|
|
gboolean paint_equal;
|
|
|
|
if (!old_theme_node ||
|
|
!st_theme_node_geometry_equal (old_theme_node, new_theme_node))
|
|
clutter_actor_queue_relayout ((ClutterActor *) widget);
|
|
|
|
transition_duration = st_theme_node_get_transition_duration (new_theme_node);
|
|
|
|
paint_equal = old_theme_node && st_theme_node_paint_equal (old_theme_node, new_theme_node);
|
|
|
|
if (paint_equal)
|
|
st_theme_node_copy_cached_paint_state (new_theme_node, old_theme_node);
|
|
|
|
if (transition_duration > 0)
|
|
{
|
|
if (widget->priv->transition_animation != NULL)
|
|
{
|
|
st_theme_node_transition_update (widget->priv->transition_animation,
|
|
new_theme_node);
|
|
}
|
|
else if (old_theme_node && !paint_equal)
|
|
{
|
|
/* Since our transitions are only of the painting done by StThemeNode, we
|
|
* only want to start a transition when what is painted changes; if
|
|
* other visual aspects like the foreground color of a label change,
|
|
* we can't animate that anyways.
|
|
*/
|
|
|
|
widget->priv->transition_animation =
|
|
st_theme_node_transition_new (old_theme_node,
|
|
new_theme_node,
|
|
transition_duration);
|
|
|
|
g_signal_connect (widget->priv->transition_animation, "completed",
|
|
G_CALLBACK (on_transition_completed), widget);
|
|
g_signal_connect_swapped (widget->priv->transition_animation,
|
|
"new-frame",
|
|
G_CALLBACK (clutter_actor_queue_redraw),
|
|
widget);
|
|
}
|
|
}
|
|
else if (widget->priv->transition_animation)
|
|
{
|
|
st_widget_remove_transition (widget);
|
|
}
|
|
|
|
g_signal_emit (widget, signals[STYLE_CHANGED], 0);
|
|
widget->priv->is_style_dirty = FALSE;
|
|
}
|
|
|
|
/**
|
|
* st_widget_ensure_style:
|
|
* @widget: A #StWidget
|
|
*
|
|
* Ensures that @widget has read its style information.
|
|
*
|
|
*/
|
|
void
|
|
st_widget_ensure_style (StWidget *widget)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
if (widget->priv->is_style_dirty)
|
|
st_widget_recompute_style (widget, NULL);
|
|
}
|
|
|
|
static StTextDirection default_direction = ST_TEXT_DIRECTION_LTR;
|
|
|
|
StTextDirection
|
|
st_widget_get_default_direction (void)
|
|
{
|
|
return default_direction;
|
|
}
|
|
|
|
void
|
|
st_widget_set_default_direction (StTextDirection dir)
|
|
{
|
|
g_return_if_fail (dir != ST_TEXT_DIRECTION_NONE);
|
|
|
|
default_direction = dir;
|
|
}
|
|
|
|
StTextDirection
|
|
st_widget_get_direction (StWidget *self)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (self), ST_TEXT_DIRECTION_LTR);
|
|
|
|
if (self->priv->direction != ST_TEXT_DIRECTION_NONE)
|
|
return self->priv->direction;
|
|
else
|
|
return default_direction;
|
|
}
|
|
|
|
void
|
|
st_widget_set_direction (StWidget *self, StTextDirection dir)
|
|
{
|
|
StTextDirection old_direction;
|
|
|
|
g_return_if_fail (ST_IS_WIDGET (self));
|
|
|
|
old_direction = st_widget_get_direction (self);
|
|
self->priv->direction = dir;
|
|
|
|
if (old_direction != st_widget_get_direction (self))
|
|
st_widget_style_changed (self);
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_track_hover:
|
|
* @widget: A #StWidget
|
|
* @track_hover: %TRUE if the widget should track the pointer hover state
|
|
*
|
|
* Enables hover tracking on the #StWidget.
|
|
*
|
|
* If hover tracking is enabled, and the widget is visible and
|
|
* reactive, then @widget's #StWidget:hover property will be updated
|
|
* automatically to reflect whether the pointer is in @widget (or one
|
|
* of its children), and @widget's #StWidget:pseudo-class will have
|
|
* the "hover" class added and removed from it accordingly.
|
|
*
|
|
* Note that currently it is not possible to correctly track the hover
|
|
* state when another actor has a pointer grab. You can use
|
|
* st_widget_sync_hover() to update the property manually in this
|
|
* case.
|
|
*/
|
|
void
|
|
st_widget_set_track_hover (StWidget *widget,
|
|
gboolean track_hover)
|
|
{
|
|
StWidgetPrivate *priv;
|
|
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
priv = widget->priv;
|
|
|
|
if (priv->track_hover != track_hover)
|
|
{
|
|
priv->track_hover = track_hover;
|
|
g_object_notify (G_OBJECT (widget), "track-hover");
|
|
|
|
if (priv->track_hover)
|
|
st_widget_sync_hover (widget);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_track_hover:
|
|
* @widget: A #StWidget
|
|
*
|
|
* Returns the current value of the track-hover property. See
|
|
* st_widget_set_track_hover() for more information.
|
|
*
|
|
* Returns: current value of track-hover on @widget
|
|
*/
|
|
gboolean
|
|
st_widget_get_track_hover (StWidget *widget)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
|
|
|
|
return widget->priv->track_hover;
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_hover:
|
|
* @widget: A #StWidget
|
|
* @hover: whether the pointer is hovering over the widget
|
|
*
|
|
* Sets @widget's hover property and adds or removes "hover" from its
|
|
* pseudo class accordingly.
|
|
*
|
|
* If you have set #StWidget:track-hover, you should not need to call
|
|
* this directly. You can call st_widget_sync_hover() if the hover
|
|
* state might be out of sync due to another actor's pointer grab.
|
|
*/
|
|
void
|
|
st_widget_set_hover (StWidget *widget,
|
|
gboolean hover)
|
|
{
|
|
StWidgetPrivate *priv;
|
|
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
priv = widget->priv;
|
|
|
|
if (priv->hover != hover)
|
|
{
|
|
priv->hover = hover;
|
|
if (priv->hover)
|
|
st_widget_add_style_pseudo_class (widget, "hover");
|
|
else
|
|
st_widget_remove_style_pseudo_class (widget, "hover");
|
|
g_object_notify (G_OBJECT (widget), "hover");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_sync_hover:
|
|
* @widget: A #StWidget
|
|
*
|
|
* Sets @widget's hover state according to the current pointer
|
|
* position. This can be used to ensure that it is correct after
|
|
* (or during) a pointer grab.
|
|
*/
|
|
void
|
|
st_widget_sync_hover (StWidget *widget)
|
|
{
|
|
ClutterDeviceManager *device_manager;
|
|
ClutterInputDevice *pointer;
|
|
ClutterActor *pointer_actor;
|
|
|
|
device_manager = clutter_device_manager_get_default ();
|
|
pointer = clutter_device_manager_get_core_device (device_manager,
|
|
CLUTTER_POINTER_DEVICE);
|
|
pointer_actor = clutter_input_device_get_pointer_actor (pointer);
|
|
if (pointer_actor)
|
|
st_widget_set_hover (widget, clutter_actor_contains (CLUTTER_ACTOR (widget), pointer_actor));
|
|
else
|
|
st_widget_set_hover (widget, FALSE);
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_hover:
|
|
* @widget: A #StWidget
|
|
*
|
|
* If #StWidget:track-hover is set, this returns whether the pointer
|
|
* is currently over the widget.
|
|
*
|
|
* Returns: current value of hover on @widget
|
|
*/
|
|
gboolean
|
|
st_widget_get_hover (StWidget *widget)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
|
|
|
|
return widget->priv->hover;
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_can_focus:
|
|
* @widget: A #StWidget
|
|
* @can_focus: %TRUE if the widget can receive keyboard focus
|
|
* via keyboard navigation
|
|
*
|
|
* Marks @widget as being able to receive keyboard focus via
|
|
* keyboard navigation.
|
|
*/
|
|
void
|
|
st_widget_set_can_focus (StWidget *widget,
|
|
gboolean can_focus)
|
|
{
|
|
StWidgetPrivate *priv;
|
|
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
priv = widget->priv;
|
|
|
|
if (priv->can_focus != can_focus)
|
|
{
|
|
priv->can_focus = can_focus;
|
|
g_object_notify (G_OBJECT (widget), "can-focus");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_can_focus:
|
|
* @widget: A #StWidget
|
|
*
|
|
* Returns the current value of the can-focus property. See
|
|
* st_widget_set_can_focus() for more information.
|
|
*
|
|
* Returns: current value of can-focus on @widget
|
|
*/
|
|
gboolean
|
|
st_widget_get_can_focus (StWidget *widget)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
|
|
|
|
return widget->priv->can_focus;
|
|
}
|
|
|
|
/* filter @children to contain only only actors that overlap @rbox
|
|
* when moving in @direction. (Assuming no transformations.)
|
|
*/
|
|
static GList *
|
|
filter_by_position (GList *children,
|
|
ClutterActorBox *rbox,
|
|
GtkDirectionType direction)
|
|
{
|
|
ClutterActorBox cbox;
|
|
GList *l, *ret;
|
|
ClutterActor *child;
|
|
|
|
for (l = children, ret = NULL; l; l = l->next)
|
|
{
|
|
child = l->data;
|
|
clutter_actor_get_allocation_box (child, &cbox);
|
|
|
|
/* Filter out children if they are in the wrong direction from
|
|
* @rbox, or if they don't overlap it. To account for floating-
|
|
* point imprecision, an actor is "down" (etc.) from an another
|
|
* actor even if it overlaps it by up to 0.1 pixels.
|
|
*/
|
|
switch (direction)
|
|
{
|
|
case GTK_DIR_UP:
|
|
if (cbox.y2 > rbox->y1 + 0.1)
|
|
continue;
|
|
if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
|
|
continue;
|
|
break;
|
|
|
|
case GTK_DIR_DOWN:
|
|
if (cbox.y1 < rbox->y2 - 0.1)
|
|
continue;
|
|
if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
|
|
continue;
|
|
break;
|
|
|
|
case GTK_DIR_LEFT:
|
|
if (cbox.x2 > rbox->x1 + 0.1)
|
|
continue;
|
|
if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1)
|
|
continue;
|
|
break;
|
|
|
|
case GTK_DIR_RIGHT:
|
|
if (cbox.x1 < rbox->x2 - 0.1)
|
|
continue;
|
|
if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1)
|
|
continue;
|
|
break;
|
|
|
|
default:
|
|
g_return_val_if_reached (NULL);
|
|
}
|
|
|
|
ret = g_list_prepend (ret, child);
|
|
}
|
|
|
|
g_list_free (children);
|
|
return ret;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
GtkDirectionType direction;
|
|
ClutterActorBox box;
|
|
} StWidgetChildSortData;
|
|
|
|
static int
|
|
sort_by_position (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer user_data)
|
|
{
|
|
ClutterActor *actor_a = (ClutterActor *)a;
|
|
ClutterActor *actor_b = (ClutterActor *)b;
|
|
StWidgetChildSortData *sort_data = user_data;
|
|
GtkDirectionType direction = sort_data->direction;
|
|
ClutterActorBox abox, bbox;
|
|
int ax, ay, bx, by;
|
|
int cmp, fmid;
|
|
|
|
/* Determine the relationship, relative to motion in @direction, of
|
|
* the center points of the two actors. Eg, for %GTK_DIR_UP, we
|
|
* return a negative number if @actor_a's center is below @actor_b's
|
|
* center, and postive if vice versa, which will result in an
|
|
* overall list sorted bottom-to-top.
|
|
*/
|
|
|
|
clutter_actor_get_allocation_box (actor_a, &abox);
|
|
ax = (int)(abox.x1 + abox.x2) / 2;
|
|
ay = (int)(abox.y1 + abox.y2) / 2;
|
|
clutter_actor_get_allocation_box (actor_b, &bbox);
|
|
bx = (int)(bbox.x1 + bbox.x2) / 2;
|
|
by = (int)(bbox.y1 + bbox.y2) / 2;
|
|
|
|
switch (direction)
|
|
{
|
|
case GTK_DIR_UP:
|
|
cmp = by - ay;
|
|
break;
|
|
case GTK_DIR_DOWN:
|
|
cmp = ay - by;
|
|
break;
|
|
case GTK_DIR_LEFT:
|
|
cmp = bx - ax;
|
|
break;
|
|
case GTK_DIR_RIGHT:
|
|
cmp = ax - bx;
|
|
break;
|
|
default:
|
|
g_return_val_if_reached (0);
|
|
}
|
|
|
|
if (cmp)
|
|
return cmp;
|
|
|
|
/* If two actors have the same center on the axis being sorted,
|
|
* prefer the one that is closer to the center of the current focus
|
|
* actor on the other axis. Eg, for %GTK_DIR_UP, prefer whichever
|
|
* of @actor_a and @actor_b has a horizontal center closest to the
|
|
* current focus actor's horizontal center.
|
|
*
|
|
* (This matches GTK's behavior.)
|
|
*/
|
|
switch (direction)
|
|
{
|
|
case GTK_DIR_UP:
|
|
case GTK_DIR_DOWN:
|
|
fmid = (int)(sort_data->box.x1 + sort_data->box.x2) / 2;
|
|
return abs (ax - fmid) - abs (bx - fmid);
|
|
case GTK_DIR_LEFT:
|
|
case GTK_DIR_RIGHT:
|
|
fmid = (int)(sort_data->box.y1 + sort_data->box.y2) / 2;
|
|
return abs (ay - fmid) - abs (by - fmid);
|
|
default:
|
|
g_return_val_if_reached (0);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
st_widget_real_navigate_focus (StWidget *widget,
|
|
ClutterActor *from,
|
|
GtkDirectionType direction)
|
|
{
|
|
ClutterActor *widget_actor, *focus_child;
|
|
GList *children, *l;
|
|
|
|
widget_actor = CLUTTER_ACTOR (widget);
|
|
if (from == widget_actor)
|
|
return FALSE;
|
|
|
|
/* Figure out if @from is a descendant of @widget, and if so,
|
|
* set @focus_child to the immediate child of @widget that
|
|
* contains (or *is*) @from.
|
|
*/
|
|
focus_child = from;
|
|
while (focus_child && clutter_actor_get_parent (focus_child) != widget_actor)
|
|
focus_child = clutter_actor_get_parent (focus_child);
|
|
|
|
if (widget->priv->can_focus)
|
|
{
|
|
if (!focus_child)
|
|
{
|
|
/* Accept focus from outside */
|
|
clutter_actor_grab_key_focus (widget_actor);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* Yield focus from within: since @widget itself is
|
|
* focusable we don't allow the focus to be navigated
|
|
* within @widget.
|
|
*/
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* See if we can navigate within @focus_child */
|
|
if (focus_child && ST_IS_WIDGET (focus_child))
|
|
{
|
|
if (st_widget_navigate_focus (ST_WIDGET (focus_child), from, direction, FALSE))
|
|
return TRUE;
|
|
}
|
|
|
|
/* At this point we know that we want to navigate focus to one of
|
|
* @widget's immediate children; the next one after @focus_child,
|
|
* or the first one if @focus_child is %NULL. (With "next" and
|
|
* "first" being determined by @direction.)
|
|
*/
|
|
|
|
children = st_widget_get_focus_chain (widget);
|
|
if (direction == GTK_DIR_TAB_FORWARD ||
|
|
direction == GTK_DIR_TAB_BACKWARD)
|
|
{
|
|
if (direction == GTK_DIR_TAB_BACKWARD)
|
|
children = g_list_reverse (children);
|
|
|
|
if (focus_child)
|
|
{
|
|
/* Remove focus_child and any earlier children */
|
|
while (children && children->data != focus_child)
|
|
children = g_list_delete_link (children, children);
|
|
if (children)
|
|
children = g_list_delete_link (children, children);
|
|
}
|
|
}
|
|
else /* direction is an arrow key, not tab */
|
|
{
|
|
StWidgetChildSortData sort_data;
|
|
|
|
/* Compute the allocation box of the previous focused actor, in
|
|
* @widget's coordinate space. If there was no previous focus,
|
|
* use the coordinates of the appropriate edge of @widget.
|
|
*
|
|
* Note that all of this code assumes the actors are not
|
|
* transformed (or at most, they are all scaled by the same
|
|
* amount). If @widget or any of its children is rotated, or
|
|
* any child is inconsistently scaled, then the focus chain will
|
|
* probably be unpredictable.
|
|
*/
|
|
if (focus_child)
|
|
{
|
|
clutter_actor_get_allocation_box (focus_child, &sort_data.box);
|
|
}
|
|
else
|
|
{
|
|
clutter_actor_get_allocation_box (CLUTTER_ACTOR (widget), &sort_data.box);
|
|
switch (direction)
|
|
{
|
|
case GTK_DIR_UP:
|
|
sort_data.box.y1 = sort_data.box.y2;
|
|
break;
|
|
case GTK_DIR_DOWN:
|
|
sort_data.box.y2 = sort_data.box.y1;
|
|
break;
|
|
case GTK_DIR_LEFT:
|
|
sort_data.box.x1 = sort_data.box.x2;
|
|
break;
|
|
case GTK_DIR_RIGHT:
|
|
sort_data.box.x2 = sort_data.box.x1;
|
|
break;
|
|
default:
|
|
g_warn_if_reached ();
|
|
}
|
|
}
|
|
sort_data.direction = direction;
|
|
|
|
if (focus_child)
|
|
children = filter_by_position (children, &sort_data.box, direction);
|
|
if (children)
|
|
children = g_list_sort_with_data (children, sort_by_position, &sort_data);
|
|
}
|
|
|
|
/* Now try each child in turn */
|
|
for (l = children; l; l = l->next)
|
|
{
|
|
if (ST_IS_WIDGET (l->data))
|
|
{
|
|
if (st_widget_navigate_focus (l->data, from, direction, FALSE))
|
|
{
|
|
g_list_free (children);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_free (children);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
* st_widget_navigate_focus:
|
|
* @widget: the "top level" container
|
|
* @from: (allow-none): the actor that the focus is coming from
|
|
* @direction: the direction focus is moving in
|
|
* @wrap_around: whether focus should wrap around
|
|
*
|
|
* Tries to update the keyboard focus within @widget in response to a
|
|
* keyboard event.
|
|
*
|
|
* If @from is a descendant of @widget, this attempts to move the
|
|
* keyboard focus to the next descendant of @widget (in the order
|
|
* implied by @direction) that has the #StWidget:can-focus property
|
|
* set. If @from is %NULL, or outside of @widget, this attempts to
|
|
* focus either @widget itself, or its first descendant in the order
|
|
* implied by @direction.
|
|
*
|
|
* If a container type is marked #StWidget:can-focus, the expected
|
|
* behavior is that it will only take up a single slot on the focus
|
|
* chain as a whole, rather than allowing navigation between its child
|
|
* actors (or having a distinction between itself being focused and
|
|
* one of its children being focused).
|
|
*
|
|
* Some widget classes might have slightly different behavior from the
|
|
* above, where that would make more sense.
|
|
*
|
|
* If @wrap_around is %TRUE and @from is a child of @widget, but the
|
|
* widget has no further children that can accept the focus in the
|
|
* given direction, then st_widget_navigate_focus() will try a second
|
|
* time, using a %NULL @from, which should cause it to reset the focus
|
|
* to the first available widget in the given direction.
|
|
*
|
|
* Return value: %TRUE if clutter_actor_grab_key_focus() has been
|
|
* called on an actor. %FALSE if not.
|
|
*/
|
|
gboolean
|
|
st_widget_navigate_focus (StWidget *widget,
|
|
ClutterActor *from,
|
|
GtkDirectionType direction,
|
|
gboolean wrap_around)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
|
|
|
|
if (ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, from, direction))
|
|
return TRUE;
|
|
if (wrap_around && from && clutter_actor_contains (CLUTTER_ACTOR (widget), from))
|
|
return ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, NULL, direction);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
append_actor_text (GString *desc,
|
|
ClutterActor *actor)
|
|
{
|
|
if (CLUTTER_IS_TEXT (actor))
|
|
{
|
|
g_string_append_printf (desc, " (\"%s\")",
|
|
clutter_text_get_text (CLUTTER_TEXT (actor)));
|
|
return TRUE;
|
|
}
|
|
else if (ST_IS_LABEL (actor))
|
|
{
|
|
g_string_append_printf (desc, " (\"%s\")",
|
|
st_label_get_text (ST_LABEL (actor)));
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* st_describe_actor:
|
|
* @actor: a #ClutterActor
|
|
*
|
|
* Creates a string describing @actor, for use in debugging. This
|
|
* includes the class name and actor name (if any), plus if @actor
|
|
* is an #StWidget, its style class and pseudo class names.
|
|
*
|
|
* Return value: the debug name.
|
|
*/
|
|
char *
|
|
st_describe_actor (ClutterActor *actor)
|
|
{
|
|
GString *desc;
|
|
const char *name;
|
|
int i;
|
|
|
|
if (!actor)
|
|
return g_strdup ("[null]");
|
|
|
|
desc = g_string_new (NULL);
|
|
g_string_append_printf (desc, "[%p %s", actor,
|
|
G_OBJECT_TYPE_NAME (actor));
|
|
|
|
if (ST_IS_WIDGET (actor))
|
|
{
|
|
const char *style_class = st_widget_get_style_class_name (ST_WIDGET (actor));
|
|
const char *pseudo_class = st_widget_get_style_pseudo_class (ST_WIDGET (actor));
|
|
char **classes;
|
|
|
|
if (style_class)
|
|
{
|
|
classes = g_strsplit (style_class, ",", -1);
|
|
for (i = 0; classes[i]; i++)
|
|
{
|
|
g_strchug (classes[i]);
|
|
g_string_append_printf (desc, ".%s", classes[i]);
|
|
}
|
|
g_strfreev (classes);
|
|
}
|
|
|
|
if (pseudo_class)
|
|
{
|
|
classes = g_strsplit (pseudo_class, ",", -1);
|
|
for (i = 0; classes[i]; i++)
|
|
{
|
|
g_strchug (classes[i]);
|
|
g_string_append_printf (desc, ":%s", classes[i]);
|
|
}
|
|
g_strfreev (classes);
|
|
}
|
|
}
|
|
|
|
name = clutter_actor_get_name (actor);
|
|
if (name)
|
|
g_string_append_printf (desc, " \"%s\"", name);
|
|
|
|
if (!append_actor_text (desc, actor))
|
|
{
|
|
GList *children, *l;
|
|
|
|
/* Do a limited search of @actor's children looking for a label */
|
|
children = clutter_actor_get_children (actor);
|
|
for (l = children, i = 0; l && i < 20; l = l->next, i++)
|
|
{
|
|
if (append_actor_text (desc, l->data))
|
|
break;
|
|
children = g_list_concat (children, clutter_actor_get_children (l->data));
|
|
}
|
|
g_list_free (children);
|
|
}
|
|
|
|
g_string_append_c (desc, ']');
|
|
|
|
return g_string_free (desc, FALSE);
|
|
}
|
|
|
|
/**
|
|
* st_set_slow_down_factor:
|
|
*
|
|
* @factor: new slow-down factor
|
|
*
|
|
* Set a global factor applied to all animation durations
|
|
*/
|
|
void
|
|
st_set_slow_down_factor (gfloat factor)
|
|
{
|
|
st_slow_down_factor = factor;
|
|
}
|
|
|
|
/**
|
|
* st_get_slow_down_factor:
|
|
*
|
|
* Returns: the global factor applied to all animation durations
|
|
*/
|
|
gfloat
|
|
st_get_slow_down_factor ()
|
|
{
|
|
return st_slow_down_factor;
|
|
}
|
|
|
|
|
|
/**
|
|
* st_widget_get_label_actor:
|
|
* @widget: a #StWidget
|
|
*
|
|
* Gets the label that identifies @widget if it is defined
|
|
*
|
|
* Return value: (transfer none): the label that identifies the widget
|
|
*/
|
|
ClutterActor *
|
|
st_widget_get_label_actor (StWidget *widget)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
|
|
|
|
return widget->priv->label_actor;
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_label_actor:
|
|
* @widget: a #StWidget
|
|
* @label: a #ClutterActor
|
|
*
|
|
* Sets @label as the #ClutterActor that identifies (labels)
|
|
* @widget. @label can be %NULL to indicate that @widget is not
|
|
* labelled any more
|
|
*/
|
|
|
|
void
|
|
st_widget_set_label_actor (StWidget *widget,
|
|
ClutterActor *label)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
if (widget->priv->label_actor != label)
|
|
{
|
|
if (widget->priv->label_actor)
|
|
g_object_unref (widget->priv->label_actor);
|
|
|
|
if (label != NULL)
|
|
widget->priv->label_actor = g_object_ref (label);
|
|
else
|
|
widget->priv->label_actor = NULL;
|
|
|
|
g_object_notify (G_OBJECT (widget), "label-actor");
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*************************** ACCESSIBILITY SUPPORT ****************************/
|
|
/******************************************************************************/
|
|
|
|
/* GObject */
|
|
|
|
static void st_widget_accessible_class_init (StWidgetAccessibleClass *klass);
|
|
static void st_widget_accessible_init (StWidgetAccessible *widget);
|
|
static void st_widget_accessible_dispose (GObject *gobject);
|
|
|
|
/* AtkObject */
|
|
static AtkStateSet *st_widget_accessible_ref_state_set (AtkObject *obj);
|
|
static void st_widget_accessible_initialize (AtkObject *obj,
|
|
gpointer data);
|
|
|
|
/* Private methods */
|
|
static void on_pseudo_class_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer data);
|
|
static void on_can_focus_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer data);
|
|
static void on_label_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer data);
|
|
static void check_selected (StWidgetAccessible *self,
|
|
StWidget *widget);
|
|
static void check_labels (StWidgetAccessible *self,
|
|
StWidget *widget);
|
|
|
|
G_DEFINE_TYPE (StWidgetAccessible, st_widget_accessible, CALLY_TYPE_ACTOR)
|
|
|
|
#define ST_WIDGET_ACCESSIBLE_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_WIDGET_ACCESSIBLE, \
|
|
StWidgetAccessiblePrivate))
|
|
|
|
struct _StWidgetAccessiblePrivate
|
|
{
|
|
/* Cached values (used to avoid extra notifications) */
|
|
gboolean selected;
|
|
|
|
/* The current_label. Right now there are the proper atk
|
|
* relationships between this object and the label
|
|
*/
|
|
AtkObject *current_label;
|
|
};
|
|
|
|
|
|
static AtkObject *
|
|
st_widget_get_accessible (ClutterActor *actor)
|
|
{
|
|
StWidget *widget = NULL;
|
|
|
|
g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
|
|
|
|
widget = ST_WIDGET (actor);
|
|
|
|
if (widget->priv->accessible == NULL)
|
|
{
|
|
widget->priv->accessible =
|
|
g_object_new (ST_WIDGET_GET_CLASS (widget)->get_accessible_type (),
|
|
NULL);
|
|
|
|
atk_object_initialize (widget->priv->accessible, actor);
|
|
}
|
|
|
|
return widget->priv->accessible;
|
|
}
|
|
|
|
|
|
static void
|
|
st_widget_accessible_class_init (StWidgetAccessibleClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->dispose = st_widget_accessible_dispose;
|
|
|
|
atk_class->ref_state_set = st_widget_accessible_ref_state_set;
|
|
atk_class->initialize = st_widget_accessible_initialize;
|
|
|
|
g_type_class_add_private (gobject_class, sizeof (StWidgetAccessiblePrivate));
|
|
}
|
|
|
|
static void
|
|
st_widget_accessible_init (StWidgetAccessible *self)
|
|
{
|
|
StWidgetAccessiblePrivate *priv = ST_WIDGET_ACCESSIBLE_GET_PRIVATE (self);
|
|
|
|
self->priv = priv;
|
|
}
|
|
|
|
static void
|
|
st_widget_accessible_dispose (GObject *gobject)
|
|
{
|
|
StWidgetAccessible *self = ST_WIDGET_ACCESSIBLE (gobject);
|
|
|
|
if (self->priv->current_label)
|
|
{
|
|
g_object_unref (self->priv->current_label);
|
|
self->priv->current_label = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
st_widget_accessible_initialize (AtkObject *obj,
|
|
gpointer data)
|
|
{
|
|
ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->initialize (obj, data);
|
|
|
|
g_signal_connect (data, "notify::pseudo-class",
|
|
G_CALLBACK (on_pseudo_class_notify),
|
|
obj);
|
|
|
|
g_signal_connect (data, "notify::can-focus",
|
|
G_CALLBACK (on_can_focus_notify),
|
|
obj);
|
|
|
|
g_signal_connect (data, "notify::label-actor",
|
|
G_CALLBACK (on_label_notify),
|
|
obj);
|
|
|
|
/* Check the cached selected state and notify the first selection.
|
|
* Ie: it is required to ensure a first notification when Alt+Tab
|
|
* popup appears
|
|
*/
|
|
check_selected (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
|
|
check_labels (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
|
|
}
|
|
|
|
static AtkStateSet *
|
|
st_widget_accessible_ref_state_set (AtkObject *obj)
|
|
{
|
|
AtkStateSet *result = NULL;
|
|
ClutterActor *actor = NULL;
|
|
StWidget *widget = NULL;
|
|
StWidgetAccessible *self = NULL;
|
|
|
|
result = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->ref_state_set (obj);
|
|
|
|
actor = CLUTTER_ACTOR (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
|
|
|
|
if (actor == NULL) /* State is defunct */
|
|
return result;
|
|
|
|
widget = ST_WIDGET (actor);
|
|
self = ST_WIDGET_ACCESSIBLE (obj);
|
|
|
|
/* priv->selected should be properly updated on the
|
|
* ATK_STATE_SELECTED notification callbacks
|
|
*/
|
|
if (self->priv->selected)
|
|
atk_state_set_add_state (result, ATK_STATE_SELECTED);
|
|
|
|
/* On clutter there isn't any tip to know if a actor is focusable or
|
|
* not, anyone can receive the key_focus. For this reason
|
|
* cally_actor sets any actor as FOCUSABLE. This is not the case on
|
|
* St, where we have can_focus. But this means that we need to
|
|
* remove the state FOCUSABLE if it is not focusable
|
|
*/
|
|
if (st_widget_get_can_focus (widget))
|
|
atk_state_set_add_state (result, ATK_STATE_FOCUSABLE);
|
|
else
|
|
atk_state_set_remove_state (result, ATK_STATE_FOCUSABLE);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
on_pseudo_class_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
check_selected (ST_WIDGET_ACCESSIBLE (data),
|
|
ST_WIDGET (gobject));
|
|
}
|
|
|
|
/*
|
|
* This method checks if the widget is selected, and notify a atk
|
|
* state change if required
|
|
*
|
|
* In order to decide if there was a selection, we use the current
|
|
* pseudo-class of the widget searching for "selected", the current
|
|
* homogeneus way to check if a item is selected (see bug 637830)
|
|
*
|
|
* In a ideal world we would have a more standard way to check if the
|
|
* item is selected or not, like the widget-context (as in the case of
|
|
* gtktreeview-cells), or something like the property "can-focus". But
|
|
* for the moment this is enough, and we can update that in the future
|
|
* if required.
|
|
*/
|
|
static void
|
|
check_selected (StWidgetAccessible *self,
|
|
StWidget *widget)
|
|
{
|
|
gboolean found = FALSE;
|
|
|
|
found = st_widget_has_style_pseudo_class (widget,
|
|
"selected");
|
|
|
|
if (found != self->priv->selected)
|
|
{
|
|
self->priv->selected = found;
|
|
atk_object_notify_state_change (ATK_OBJECT (self),
|
|
ATK_STATE_SELECTED,
|
|
found);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_can_focus_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
gboolean can_focus = st_widget_get_can_focus (ST_WIDGET (gobject));
|
|
|
|
atk_object_notify_state_change (ATK_OBJECT (data),
|
|
ATK_STATE_FOCUSABLE, can_focus);
|
|
}
|
|
|
|
static void
|
|
on_label_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
check_labels (ST_WIDGET_ACCESSIBLE (data), ST_WIDGET (gobject));
|
|
}
|
|
|
|
static void
|
|
check_labels (StWidgetAccessible *widget_accessible,
|
|
StWidget *widget)
|
|
{
|
|
ClutterActor *label = NULL;
|
|
AtkObject *label_accessible = NULL;
|
|
|
|
/* We only call this method at startup, and when the label changes,
|
|
* so it is fine to remove the previous relationships if we have the
|
|
* current_label by default
|
|
*/
|
|
if (widget_accessible->priv->current_label != NULL)
|
|
{
|
|
AtkObject *previous_label = widget_accessible->priv->current_label;
|
|
|
|
atk_object_remove_relationship (ATK_OBJECT (widget_accessible),
|
|
ATK_RELATION_LABELLED_BY,
|
|
previous_label);
|
|
|
|
atk_object_remove_relationship (previous_label,
|
|
ATK_RELATION_LABEL_FOR,
|
|
ATK_OBJECT (widget_accessible));
|
|
|
|
g_object_unref (previous_label);
|
|
}
|
|
|
|
label = st_widget_get_label_actor (widget);
|
|
if (label == NULL)
|
|
{
|
|
widget_accessible->priv->current_label = NULL;
|
|
}
|
|
else
|
|
{
|
|
label_accessible = clutter_actor_get_accessible (label);
|
|
widget_accessible->priv->current_label = g_object_ref (label_accessible);
|
|
|
|
atk_object_add_relationship (ATK_OBJECT (widget_accessible),
|
|
ATK_RELATION_LABELLED_BY,
|
|
label_accessible);
|
|
|
|
atk_object_add_relationship (label_accessible,
|
|
ATK_RELATION_LABEL_FOR,
|
|
ATK_OBJECT (widget_accessible));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_focus_chain:
|
|
* @widget: An #StWidget
|
|
*
|
|
* Gets a list of the focusable children of @widget, in "Tab"
|
|
* order. By default, this returns all visible
|
|
* (as in CLUTTER_ACTOR_IS_VISIBLE()) children of @widget.
|
|
*
|
|
* Returns: (element-type Clutter.Actor) (transfer container):
|
|
* @widget's focusable children
|
|
*/
|
|
GList *
|
|
st_widget_get_focus_chain (StWidget *widget)
|
|
{
|
|
return ST_WIDGET_GET_CLASS (widget)->get_focus_chain (widget);
|
|
}
|