49064ed56d
This ensures that two widgets sharing the same theme node won't trample on each other's prerendered materials if the actors are of different sizes. This also tries to be very careful to share as much as possible during a transition. This has the side effect that if a widget changes state a bunch of times, we won't cache every state. Since we expect that state changes are infrequent and that most cases we'll be able to use the texture cache to do most of the heavy lifting, this cost is much more insignificant than rendering a number of different actors with the same theme node and different sizes. https://bugzilla.gnome.org/show_bug.cgi?id=697274
2834 lines
82 KiB
C
2834 lines
82 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
|
|
* Copyright 2012 Igalia, S.L.
|
|
*
|
|
* 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>
|
|
#include <atk/atk-enum-types.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;
|
|
|
|
AtkObject *accessible;
|
|
AtkRole accessible_role;
|
|
AtkStateSet *local_state_set;
|
|
|
|
ClutterActor *label_actor;
|
|
gchar *accessible_name;
|
|
|
|
/* 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;
|
|
|
|
StThemeNodePaintState paint_state;
|
|
};
|
|
|
|
/**
|
|
* 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,
|
|
PROP_ACCESSIBLE_ROLE,
|
|
PROP_ACCESSIBLE_NAME
|
|
};
|
|
|
|
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;
|
|
|
|
case PROP_ACCESSIBLE_ROLE:
|
|
st_widget_set_accessible_role (actor, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_ACCESSIBLE_NAME:
|
|
st_widget_set_accessible_name (actor, g_value_get_string (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;
|
|
|
|
case PROP_ACCESSIBLE_ROLE:
|
|
g_value_set_enum (value, st_widget_get_accessible_role (actor));
|
|
break;
|
|
|
|
case PROP_ACCESSIBLE_NAME:
|
|
g_value_set_string (value, priv->accessible_name);
|
|
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_texture_cache_changed (StTextureCache *cache,
|
|
const char *uri,
|
|
gpointer user_data)
|
|
{
|
|
StWidget *actor = ST_WIDGET (user_data);
|
|
StThemeNode *node = actor->priv->theme_node;
|
|
StBorderImage *border_image;
|
|
char *path;
|
|
gboolean changed;
|
|
|
|
if (node == NULL)
|
|
return;
|
|
|
|
path = g_filename_from_uri (uri, NULL, NULL);
|
|
|
|
changed = g_strcmp0 (st_theme_node_get_background_image (node), path) == 0;
|
|
|
|
border_image = st_theme_node_get_border_image (node);
|
|
if (!changed && border_image)
|
|
changed = strcmp (st_border_image_get_filename (border_image), path) == 0;
|
|
|
|
g_free (path);
|
|
|
|
if (!changed)
|
|
return;
|
|
|
|
st_theme_node_paint_state_invalidate (&actor->priv->paint_state);
|
|
|
|
if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR (actor)))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (actor));
|
|
}
|
|
|
|
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_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_signal_handlers_disconnect_by_func (st_texture_cache_get_default (),
|
|
st_widget_texture_cache_changed,
|
|
actor);
|
|
|
|
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_unref (priv->local_state_set);
|
|
g_free (priv->accessible_name);
|
|
g_free (priv->inline_style);
|
|
|
|
st_theme_node_paint_state_free (&priv->paint_state);
|
|
|
|
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,
|
|
&widget->priv->paint_state,
|
|
&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)
|
|
{
|
|
StThemeContext *context;
|
|
StThemeNode *tmp_node;
|
|
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 (clutter_actor_get_text_direction (CLUTTER_ACTOR (widget)) == CLUTTER_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;
|
|
|
|
context = st_theme_context_get_for_stage (stage);
|
|
tmp_node = st_theme_node_new (context, 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);
|
|
|
|
priv->theme_node = g_object_ref (st_theme_context_intern_node (context,
|
|
tmp_node));
|
|
g_object_unref (tmp_node);
|
|
}
|
|
|
|
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);
|
|
|
|
if (!clutter_actor_get_clip_to_allocation (self))
|
|
{
|
|
ClutterActor *child;
|
|
/* Based on ClutterGroup/ClutterBox; include the children's
|
|
* paint volumes, since they may paint outside our allocation.
|
|
*/
|
|
for (child = clutter_actor_get_first_child (self);
|
|
child != NULL;
|
|
child = clutter_actor_get_next_sibling (child))
|
|
{
|
|
const ClutterPaintVolume *child_volume;
|
|
|
|
if (!CLUTTER_ACTOR_IS_VISIBLE (child))
|
|
continue;
|
|
|
|
child_volume = clutter_actor_get_transformed_paint_volume (child, self);
|
|
if (!child_volume)
|
|
return FALSE;
|
|
|
|
clutter_paint_volume_union (volume, child_volume);
|
|
}
|
|
}
|
|
|
|
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:accessible-role:
|
|
*
|
|
* The accessible role of this object
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_ACCESSIBLE_ROLE,
|
|
g_param_spec_enum ("accessible-role",
|
|
"Accessible Role",
|
|
"The accessible role of this object",
|
|
ATK_TYPE_ROLE,
|
|
ATK_ROLE_INVALID,
|
|
G_PARAM_READWRITE));
|
|
|
|
|
|
/**
|
|
* StWidget:accessible-name:
|
|
*
|
|
* Object instance's name for assistive technology access.
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_ACCESSIBLE_NAME,
|
|
g_param_spec_string ("accessible-name",
|
|
"Accessible name",
|
|
"Object instance's name for assistive technology access.",
|
|
NULL,
|
|
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;
|
|
|
|
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 (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;
|
|
|
|
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_reactive_notify (StWidget *widget,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
if (clutter_actor_get_reactive (CLUTTER_ACTOR (widget)))
|
|
st_widget_remove_style_pseudo_class (widget, "insensitive");
|
|
else
|
|
st_widget_add_style_pseudo_class (widget, "insensitive");
|
|
}
|
|
|
|
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;
|
|
priv->local_state_set = atk_state_set_new ();
|
|
|
|
/* connect style changed */
|
|
g_signal_connect (actor, "notify::name", G_CALLBACK (st_widget_name_notify), NULL);
|
|
g_signal_connect (actor, "notify::reactive", G_CALLBACK (st_widget_reactive_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);
|
|
g_signal_connect (st_texture_cache_get_default (), "texture-file-changed",
|
|
G_CALLBACK (st_widget_texture_cache_changed), actor);
|
|
|
|
st_theme_node_paint_state_init (&priv->paint_state);
|
|
}
|
|
|
|
static void
|
|
on_transition_completed (StThemeNodeTransition *transition,
|
|
StWidget *widget)
|
|
{
|
|
st_theme_node_paint_state_copy (&widget->priv->paint_state,
|
|
st_theme_node_transition_get_new_paint_state (transition));
|
|
|
|
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;
|
|
gboolean animations_enabled;
|
|
|
|
if (new_theme_node == old_theme_node)
|
|
{
|
|
widget->priv->is_style_dirty = FALSE;
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
g_object_get (gtk_settings_get_default (),
|
|
"gtk-enable-animations", &animations_enabled,
|
|
NULL);
|
|
|
|
if (animations_enabled && 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,
|
|
&widget->priv->paint_state,
|
|
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);
|
|
}
|
|
|
|
if (!paint_equal)
|
|
st_theme_node_paint_state_invalidate (&widget->priv->paint_state);
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
else
|
|
st_widget_set_hover (widget, FALSE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
ClutterVertex abs_vertices[4];
|
|
GList *l, *ret;
|
|
ClutterActor *child;
|
|
|
|
for (l = children, ret = NULL; l; l = l->next)
|
|
{
|
|
child = l->data;
|
|
clutter_actor_get_abs_allocation_vertices (child, abs_vertices);
|
|
clutter_actor_box_from_vertices (&cbox, abs_vertices);
|
|
|
|
/* 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;
|
|
break;
|
|
|
|
case GTK_DIR_DOWN:
|
|
if (cbox.y1 < rbox->y2 - 0.1)
|
|
continue;
|
|
break;
|
|
|
|
case GTK_DIR_LEFT:
|
|
if (cbox.x2 > rbox->x1 + 0.1)
|
|
continue;
|
|
break;
|
|
|
|
case GTK_DIR_RIGHT:
|
|
if (cbox.x1 < rbox->x2 - 0.1)
|
|
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;
|
|
ClutterVertex abs_vertices[4];
|
|
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_abs_allocation_vertices (actor_a, abs_vertices);
|
|
clutter_actor_box_from_vertices (&abox, abs_vertices);
|
|
ax = (int)(abox.x1 + abox.x2) / 2;
|
|
ay = (int)(abox.y1 + abox.y2) / 2;
|
|
clutter_actor_get_abs_allocation_vertices (actor_b, abs_vertices);
|
|
clutter_actor_box_from_vertices (&bbox, abs_vertices);
|
|
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)
|
|
{
|
|
if (CLUTTER_ACTOR_IS_MAPPED (widget_actor))
|
|
{
|
|
/* Accept focus from outside */
|
|
clutter_actor_grab_key_focus (widget_actor);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* Refuse to set focus on hidden actors */
|
|
return FALSE;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
children = st_widget_get_focus_chain (widget);
|
|
if (direction == GTK_DIR_TAB_FORWARD ||
|
|
direction == GTK_DIR_TAB_BACKWARD)
|
|
{
|
|
/* 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.)
|
|
*/
|
|
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;
|
|
ClutterVertex abs_vertices[4];
|
|
|
|
/* Compute the allocation box of the previous focused actor. 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 (from)
|
|
{
|
|
clutter_actor_get_abs_allocation_vertices (from, abs_vertices);
|
|
clutter_actor_box_from_vertices (&sort_data.box, abs_vertices);
|
|
}
|
|
else
|
|
{
|
|
clutter_actor_get_abs_allocation_vertices (widget_actor, abs_vertices);
|
|
clutter_actor_box_from_vertices (&sort_data.box, abs_vertices);
|
|
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 (from)
|
|
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, this attempts to focus either @widget
|
|
* itself, or its first descendant in the order implied by
|
|
* @direction. If @from is outside of @widget, it behaves as if it was
|
|
* a descendant if @direction is one of the directional arrows and as
|
|
* if it was %NULL otherwise.
|
|
*
|
|
* 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");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_accessible_name:
|
|
* @widget: widget to set the accessible name for
|
|
* @name: (allow-none): a character string to be set as the accessible name
|
|
*
|
|
* This method sets @name as the accessible name for @widget.
|
|
*
|
|
* Usually you will have no need to set the accessible name for an
|
|
* object, as usually there is a label for most of the interface
|
|
* elements. So in general it is better to just use
|
|
* @st_widget_set_label_actor. This method is only required when you
|
|
* need to set an accessible name and there is no available label
|
|
* object.
|
|
*
|
|
*/
|
|
void
|
|
st_widget_set_accessible_name (StWidget *widget,
|
|
const gchar *name)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
if (widget->priv->accessible_name != NULL)
|
|
g_free (widget->priv->accessible_name);
|
|
|
|
widget->priv->accessible_name = g_strdup (name);
|
|
g_object_notify (G_OBJECT (widget), "accessible-name");
|
|
}
|
|
|
|
/**
|
|
* st_widget_get_accessible_name:
|
|
* @widget: widget to get the accessible name for
|
|
*
|
|
* Gets the accessible name for this widget. See
|
|
* st_widget_set_accessible_name() for more information.
|
|
*
|
|
* Return value: a character string representing the accessible name
|
|
* of the widget.
|
|
*/
|
|
const gchar *
|
|
st_widget_get_accessible_name (StWidget *widget)
|
|
{
|
|
g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
|
|
|
|
return widget->priv->accessible_name;
|
|
}
|
|
|
|
/**
|
|
* st_widget_set_accessible_role:
|
|
* @widget: widget to set the accessible role for
|
|
* @role: The role to use
|
|
*
|
|
* This method sets @role as the accessible role for @widget. This
|
|
* role describes what kind of user interface element @widget is and
|
|
* is provided so that assistive technologies know how to present
|
|
* @widget to the user.
|
|
*
|
|
* Usually you will have no need to set the accessible role for an
|
|
* object, as this information is extracted from the context of the
|
|
* object (ie: a #StButton has by default a push button role). This
|
|
* method is only required when you need to redefine the role
|
|
* currently associated with the widget, for instance if it is being
|
|
* used in an unusual way (ie: a #StButton used as a togglebutton), or
|
|
* if a generic object is used directly (ie: a container as a menu
|
|
* item).
|
|
*
|
|
* If @role is #ATK_ROLE_INVALID, the role will not be changed
|
|
* and the accessible's default role will be used instead.
|
|
*/
|
|
void
|
|
st_widget_set_accessible_role (StWidget *widget,
|
|
AtkRole role)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
widget->priv->accessible_role = role;
|
|
|
|
g_object_notify (G_OBJECT (widget), "accessible-role");
|
|
}
|
|
|
|
|
|
/**
|
|
* st_widget_get_accessible_role:
|
|
* @widget: widget to get the accessible role for
|
|
*
|
|
* Gets the #AtkRole for this widget. See
|
|
* st_widget_set_accessible_role() for more information.
|
|
*
|
|
* Return value: accessible #AtkRole for this widget
|
|
*/
|
|
AtkRole
|
|
st_widget_get_accessible_role (StWidget *widget)
|
|
{
|
|
AtkObject *accessible = NULL;
|
|
AtkRole role = ATK_ROLE_INVALID;
|
|
|
|
g_return_val_if_fail (ST_IS_WIDGET (widget), ATK_ROLE_INVALID);
|
|
|
|
if (widget->priv->accessible_role != ATK_ROLE_INVALID)
|
|
role = widget->priv->accessible_role;
|
|
else if (widget->priv->accessible != NULL)
|
|
role = atk_object_get_role (accessible);
|
|
|
|
return role;
|
|
}
|
|
|
|
static void
|
|
notify_accessible_state_change (StWidget *widget,
|
|
AtkStateType state,
|
|
gboolean value)
|
|
{
|
|
if (widget->priv->accessible != NULL)
|
|
atk_object_notify_state_change (widget->priv->accessible, state, value);
|
|
}
|
|
|
|
/**
|
|
* st_widget_add_accessible_state:
|
|
* @widget: A #StWidget
|
|
* @state: #AtkStateType state to add
|
|
*
|
|
* This method adds @state as one of the accessible states for
|
|
* @widget. The list of states of a widget describes the current state
|
|
* of user interface element @widget and is provided so that assistive
|
|
* technologies know how to present @widget to the user.
|
|
*
|
|
* Usually you will have no need to add accessible states for an
|
|
* object, as the accessible object can extract most of the states
|
|
* from the object itself (ie: a #StButton knows when it is pressed).
|
|
* This method is only required when one cannot extract the
|
|
* information automatically from the object itself (i.e.: a generic
|
|
* container used as a toggle menu item will not automatically include
|
|
* the toggled state).
|
|
*
|
|
*/
|
|
void
|
|
st_widget_add_accessible_state (StWidget *widget,
|
|
AtkStateType state)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
if (atk_state_set_add_state (widget->priv->local_state_set, state))
|
|
notify_accessible_state_change (widget, state, TRUE);
|
|
}
|
|
|
|
/**
|
|
* st_widget_remove_accessible_state:
|
|
* @widget: A #StWidget
|
|
* @state: #AtkState state to remove
|
|
*
|
|
* This method removes @state as on of the accessible states for
|
|
* @widget. See st_widget_add_accessible_state() for more information.
|
|
*
|
|
*/
|
|
void
|
|
st_widget_remove_accessible_state (StWidget *widget,
|
|
AtkStateType state)
|
|
{
|
|
g_return_if_fail (ST_IS_WIDGET (widget));
|
|
|
|
if (atk_state_set_remove_state (widget->priv->local_state_set, state))
|
|
notify_accessible_state_change (widget, state, FALSE);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*************************** 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);
|
|
static AtkRole st_widget_accessible_get_role (AtkObject *obj);
|
|
|
|
/* 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_pseudo_class (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;
|
|
gboolean checked;
|
|
|
|
/* 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 const gchar *
|
|
st_widget_accessible_get_name (AtkObject *obj)
|
|
{
|
|
const gchar* name = NULL;
|
|
|
|
g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), NULL);
|
|
|
|
name = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_name (obj);
|
|
if (name == NULL)
|
|
{
|
|
StWidget *widget = NULL;
|
|
|
|
widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
|
|
|
|
if (widget == NULL)
|
|
name = NULL;
|
|
else
|
|
name = widget->priv->accessible_name;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
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;
|
|
atk_class->get_role = st_widget_accessible_get_role;
|
|
atk_class->get_name = st_widget_accessible_get_name;
|
|
|
|
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;
|
|
}
|
|
|
|
G_OBJECT_CLASS (st_widget_accessible_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
on_accessible_name_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
AtkObject *accessible)
|
|
{
|
|
g_object_notify (G_OBJECT (accessible), "accessible-name");
|
|
}
|
|
|
|
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);
|
|
|
|
g_signal_connect (data, "notify::accessible-name",
|
|
G_CALLBACK (on_accessible_name_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_pseudo_class (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;
|
|
AtkStateSet *aux_set = 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);
|
|
|
|
if (self->priv->checked)
|
|
atk_state_set_add_state (result, ATK_STATE_CHECKED);
|
|
|
|
/* 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);
|
|
|
|
/* We add the states added externally if required */
|
|
if (!atk_state_set_is_empty (widget->priv->local_state_set))
|
|
{
|
|
aux_set = atk_state_set_or_sets (result, widget->priv->local_state_set);
|
|
|
|
g_object_unref (result); /* previous result will not be used */
|
|
result = aux_set;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static AtkRole
|
|
st_widget_accessible_get_role (AtkObject *obj)
|
|
{
|
|
StWidget *widget = NULL;
|
|
|
|
g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), ATK_ROLE_INVALID);
|
|
|
|
widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
|
|
|
|
if (widget == NULL)
|
|
return ATK_ROLE_INVALID;
|
|
|
|
if (widget->priv->accessible_role != ATK_ROLE_INVALID)
|
|
return widget->priv->accessible_role;
|
|
|
|
return ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_role (obj);
|
|
}
|
|
|
|
static void
|
|
on_pseudo_class_notify (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer data)
|
|
{
|
|
check_pseudo_class (ST_WIDGET_ACCESSIBLE (data),
|
|
ST_WIDGET (gobject));
|
|
}
|
|
|
|
/*
|
|
* In some cases the only way to check some states are checking the
|
|
* pseudo-class. Like if the object is selected (see bug 637830) or if
|
|
* the object is toggled. This method also notifies a state change if
|
|
* the value is different to the one cached.
|
|
*
|
|
* We also assume that if the object uses that pseudo-class, it makes
|
|
* sense to notify that state change. It would be possible to refine
|
|
* that behaviour checking the role (ie: notify CHECKED changes only
|
|
* for CHECK_BUTTON roles).
|
|
*
|
|
* In a ideal world we would have a more standard way to get the
|
|
* state, 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_pseudo_class (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);
|
|
}
|
|
|
|
found = st_widget_has_style_pseudo_class (widget,
|
|
"checked");
|
|
if (found != self->priv->checked)
|
|
{
|
|
self->priv->checked = found;
|
|
atk_object_notify_state_change (ATK_OBJECT (self),
|
|
ATK_STATE_CHECKED,
|
|
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);
|
|
}
|