No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-widget.c: Base class for St actors
4 *
5 * Copyright 2007 OpenedHand
6 * Copyright 2008, 2009 Intel Corporation.
7 * Copyright 2009, 2010 Red Hat, Inc.
8 * Copyright 2009 Abderrahim Kitouni
9 * Copyright 2009, 2010 Florian Mç«Żllner
10 * Copyright 2010 Adel Gadllah
11 * Copyright 2012 Igalia, S.L.
12 *
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms and conditions of the GNU Lesser General Public License,
15 * version 2.1, as published by the Free Software Foundation.
16 *
17 * This program is distributed in the hope it will be useful, but WITHOUT ANY
18 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
20 * more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include <math.h>
33
34 #include <clutter/clutter.h>
35
36 #include "st-widget.h"
37
38 #include "st-label.h"
39 #include "st-private.h"
40 #include "st-texture-cache.h"
41 #include "st-theme-context.h"
42 #include "st-theme-node-transition.h"
43
44 #include "st-widget-accessible.h"
45
46 #include <gtk/gtk.h>
47 #include <atk/atk-enum-types.h>
48
49 /*
50 * Forward declaration for sake of StWidgetChild
51 */
52 struct _StWidgetPrivate
53 {
54 StTheme *theme;
55 StThemeNode *theme_node;
56 gchar *pseudo_class;
57 gchar *style_class;
58 gchar *inline_style;
59
60 StThemeNodeTransition *transition_animation;
61
62 gboolean is_stylable : 1;
63 gboolean is_style_dirty : 1;
64 gboolean draw_bg_color : 1;
65 gboolean draw_border_internal : 1;
66 gboolean track_hover : 1;
67 gboolean hover : 1;
68 gboolean can_focus : 1;
69
70 AtkObject *accessible;
71 AtkRole accessible_role;
72 AtkStateSet *local_state_set;
73
74 ClutterActor *label_actor;
75 gchar *accessible_name;
76
77 /* Even though Clutter has first_child/last_child properties,
78 * we need to keep track of the old first/last children so
79 * that we can remove the pseudo classes on them. */
80 StWidget *prev_last_child;
81 StWidget *prev_first_child;
82 };
83
84 /**
85 * SECTION:st-widget
86 * @short_description: Base class for stylable actors
87 *
88 * #StWidget is a simple abstract class on top of #ClutterActor. It
89 * provides basic themeing properties.
90 *
91 * Actors in the St library should subclass #StWidget if they plan
92 * to obey to a certain #StStyle.
93 */
94
95 enum
96 {
97 PROP_0,
98
99 PROP_THEME,
100 PROP_PSEUDO_CLASS,
101 PROP_STYLE_CLASS,
102 PROP_STYLE,
103 PROP_STYLABLE,
104 PROP_TRACK_HOVER,
105 PROP_HOVER,
106 PROP_CAN_FOCUS,
107 PROP_LABEL_ACTOR,
108 PROP_ACCESSIBLE_ROLE,
109 PROP_ACCESSIBLE_NAME
110 };
111
112 enum
113 {
114 STYLE_CHANGED,
115 POPUP_MENU,
116
117 LAST_SIGNAL
118 };
119
120 static guint signals[LAST_SIGNAL] = { 0, };
121
122 gfloat st_slow_down_factor = 1.0;
123
124 G_DEFINE_TYPE (StWidget, st_widget, CLUTTER_TYPE_ACTOR);
125
126 #define ST_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_WIDGET, StWidgetPrivate))
127
128 static void st_widget_recompute_style (StWidget *widget,
129 StThemeNode *old_theme_node);
130 static gboolean st_widget_real_navigate_focus (StWidget *widget,
131 ClutterActor *from,
132 GtkDirectionType direction);
133
134 static AtkObject * st_widget_get_accessible (ClutterActor *actor);
135
136 static void
137 st_widget_set_property (GObject *gobject,
138 guint prop_id,
139 const GValue *value,
140 GParamSpec *pspec)
141 {
142 StWidget *actor = ST_WIDGET (gobject);
143
144 switch (prop_id)
145 {
146 case PROP_THEME:
147 st_widget_set_theme (actor, g_value_get_object (value));
148 break;
149
150 case PROP_PSEUDO_CLASS:
151 st_widget_set_style_pseudo_class (actor, g_value_get_string (value));
152 break;
153
154 case PROP_STYLE_CLASS:
155 st_widget_set_style_class_name (actor, g_value_get_string (value));
156 break;
157
158 case PROP_STYLE:
159 st_widget_set_style (actor, g_value_get_string (value));
160 break;
161
162 case PROP_STYLABLE:
163 if (actor->priv->is_stylable != g_value_get_boolean (value))
164 {
165 actor->priv->is_stylable = g_value_get_boolean (value);
166 clutter_actor_queue_relayout ((ClutterActor *) gobject);
167 }
168 break;
169
170 case PROP_TRACK_HOVER:
171 st_widget_set_track_hover (actor, g_value_get_boolean (value));
172 break;
173
174 case PROP_HOVER:
175 st_widget_set_hover (actor, g_value_get_boolean (value));
176 break;
177
178 case PROP_CAN_FOCUS:
179 st_widget_set_can_focus (actor, g_value_get_boolean (value));
180 break;
181
182 case PROP_LABEL_ACTOR:
183 st_widget_set_label_actor (actor, g_value_get_object (value));
184 break;
185
186 case PROP_ACCESSIBLE_ROLE:
187 st_widget_set_accessible_role (actor, g_value_get_enum (value));
188 break;
189
190 case PROP_ACCESSIBLE_NAME:
191 st_widget_set_accessible_name (actor, g_value_get_string (value));
192 break;
193
194 default:
195 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
196 break;
197 }
198 }
199
200 static void
201 st_widget_get_property (GObject *gobject,
202 guint prop_id,
203 GValue *value,
204 GParamSpec *pspec)
205 {
206 StWidget *actor = ST_WIDGET (gobject);
207 StWidgetPrivate *priv = actor->priv;
208
209 switch (prop_id)
210 {
211 case PROP_THEME:
212 g_value_set_object (value, priv->theme);
213 break;
214
215 case PROP_PSEUDO_CLASS:
216 g_value_set_string (value, priv->pseudo_class);
217 break;
218
219 case PROP_STYLE_CLASS:
220 g_value_set_string (value, priv->style_class);
221 break;
222
223 case PROP_STYLE:
224 g_value_set_string (value, priv->inline_style);
225 break;
226
227 case PROP_STYLABLE:
228 g_value_set_boolean (value, priv->is_stylable);
229 break;
230
231 case PROP_TRACK_HOVER:
232 g_value_set_boolean (value, priv->track_hover);
233 break;
234
235 case PROP_HOVER:
236 g_value_set_boolean (value, priv->hover);
237 break;
238
239 case PROP_CAN_FOCUS:
240 g_value_set_boolean (value, priv->can_focus);
241 break;
242
243 case PROP_LABEL_ACTOR:
244 g_value_set_object (value, priv->label_actor);
245 break;
246
247 case PROP_ACCESSIBLE_ROLE:
248 g_value_set_enum (value, st_widget_get_accessible_role (actor));
249 break;
250
251 case PROP_ACCESSIBLE_NAME:
252 g_value_set_string (value, priv->accessible_name);
253 break;
254
255 default:
256 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
257 break;
258 }
259 }
260
261 static void
262 st_widget_remove_transition (StWidget *widget)
263 {
264 if (widget->priv->transition_animation)
265 {
266 g_object_run_dispose (G_OBJECT (widget->priv->transition_animation));
267 g_object_unref (widget->priv->transition_animation);
268 widget->priv->transition_animation = NULL;
269 }
270 }
271
272 static void
273 st_widget_dispose (GObject *gobject)
274 {
275 StWidget *actor = ST_WIDGET (gobject);
276 StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
277
278 if (priv->theme)
279 {
280 g_object_unref (priv->theme);
281 priv->theme = NULL;
282 }
283
284 if (priv->theme_node)
285 {
286 g_object_run_dispose (G_OBJECT (priv->theme_node));
287 g_object_unref (priv->theme_node);
288 priv->theme_node = NULL;
289 }
290
291 st_widget_remove_transition (actor);
292
293 /* The real dispose of this accessible is done on
294 * AtkGObjectAccessible weak ref callback
295 */
296 if (priv->accessible)
297 priv->accessible = NULL;
298
299 if (priv->label_actor)
300 {
301 g_object_unref (priv->label_actor);
302 priv->label_actor = NULL;
303 }
304
305 g_clear_object (&priv->prev_first_child);
306 g_clear_object (&priv->prev_last_child);
307
308 G_OBJECT_CLASS (st_widget_parent_class)->dispose (gobject);
309 }
310
311 static void
312 st_widget_finalize (GObject *gobject)
313 {
314 StWidgetPrivate *priv = ST_WIDGET (gobject)->priv;
315
316 g_free (priv->style_class);
317 g_free (priv->pseudo_class);
318 g_object_unref (priv->local_state_set);
319 g_free (priv->accessible_name);
320 g_free (priv->inline_style);
321
322 G_OBJECT_CLASS (st_widget_parent_class)->finalize (gobject);
323 }
324
325
326 static void
327 st_widget_get_preferred_width (ClutterActor *self,
328 gfloat for_height,
329 gfloat *min_width_p,
330 gfloat *natural_width_p)
331 {
332 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
333
334 st_theme_node_adjust_for_width (theme_node, &for_height);
335
336 CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_width (self, for_height, min_width_p, natural_width_p);
337
338 st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
339 }
340
341 static void
342 st_widget_get_preferred_height (ClutterActor *self,
343 gfloat for_width,
344 gfloat *min_height_p,
345 gfloat *natural_height_p)
346 {
347 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
348
349 st_theme_node_adjust_for_width (theme_node, &for_width);
350
351 CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_height (self, for_width, min_height_p, natural_height_p);
352
353 st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
354 }
355
356 static void
357 st_widget_allocate (ClutterActor *actor,
358 const ClutterActorBox *box,
359 ClutterAllocationFlags flags)
360 {
361 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
362 ClutterActorBox content_box;
363
364 /* Note that we can't just chain up to clutter_actor_real_allocate --
365 * Clutter does some dirty tricks for backwards compatibility.
366 * Clutter also passes the actor's allocation directly to the layout
367 * manager, meaning that we can't modify it for children only.
368 */
369
370 clutter_actor_set_allocation (actor, box, flags);
371
372 st_theme_node_get_content_box (theme_node, box, &content_box);
373
374 /* If we've chained up to here, we want to allocate the children using the
375 * currently installed layout manager */
376 clutter_layout_manager_allocate (clutter_actor_get_layout_manager (actor),
377 CLUTTER_CONTAINER (actor),
378 &content_box,
379 flags);
380 }
381
382 /**
383 * st_widget_paint_background:
384 * @widget: The #StWidget
385 *
386 * Paint the background of the widget. This is meant to be called by
387 * subclasses of StWiget that need to paint the background without
388 * painting children.
389 */
390 void
391 st_widget_paint_background (StWidget *widget)
392 {
393 StThemeNode *theme_node;
394 ClutterActorBox allocation;
395 guint8 opacity;
396
397 theme_node = st_widget_get_theme_node (widget);
398
399 clutter_actor_get_allocation_box (CLUTTER_ACTOR (widget), &allocation);
400
401 opacity = clutter_actor_get_paint_opacity (CLUTTER_ACTOR (widget));
402
403 if (widget->priv->transition_animation)
404 st_theme_node_transition_paint (widget->priv->transition_animation,
405 &allocation,
406 opacity);
407 else
408 st_theme_node_paint (theme_node, &allocation, opacity);
409 }
410
411 static void
412 st_widget_paint (ClutterActor *actor)
413 {
414 st_widget_paint_background (ST_WIDGET (actor));
415
416 /* Chain up so we paint children. */
417 CLUTTER_ACTOR_CLASS (st_widget_parent_class)->paint (actor);
418 }
419
420 static void
421 st_widget_parent_set (ClutterActor *widget,
422 ClutterActor *old_parent)
423 {
424 ClutterActorClass *parent_class;
425 ClutterActor *new_parent;
426
427 parent_class = CLUTTER_ACTOR_CLASS (st_widget_parent_class);
428 if (parent_class->parent_set)
429 parent_class->parent_set (widget, old_parent);
430
431 new_parent = clutter_actor_get_parent (widget);
432
433 /* don't send the style changed signal if we no longer have a parent actor */
434 if (new_parent)
435 st_widget_style_changed (ST_WIDGET (widget));
436 }
437
438 static void
439 st_widget_map (ClutterActor *actor)
440 {
441 StWidget *self = ST_WIDGET (actor);
442
443 CLUTTER_ACTOR_CLASS (st_widget_parent_class)->map (actor);
444
445 st_widget_ensure_style (self);
446 }
447
448 static void
449 st_widget_unmap (ClutterActor *actor)
450 {
451 StWidget *self = ST_WIDGET (actor);
452 StWidgetPrivate *priv = self->priv;
453
454 CLUTTER_ACTOR_CLASS (st_widget_parent_class)->unmap (actor);
455
456 if (priv->track_hover && priv->hover)
457 st_widget_set_hover (self, FALSE);
458 }
459
460 static void
461 notify_children_of_style_change (ClutterActor *self)
462 {
463 ClutterActorIter iter;
464 ClutterActor *actor;
465
466 clutter_actor_iter_init (&iter, self);
467 while (clutter_actor_iter_next (&iter, &actor))
468 {
469 if (ST_IS_WIDGET (actor))
470 st_widget_style_changed (ST_WIDGET (actor));
471 else
472 notify_children_of_style_change (actor);
473 }
474 }
475
476 static void
477 st_widget_real_style_changed (StWidget *self)
478 {
479 StWidgetPrivate *priv = ST_WIDGET (self)->priv;
480
481 /* application has request this widget is not stylable */
482 if (!priv->is_stylable)
483 return;
484
485 clutter_actor_queue_redraw ((ClutterActor *) self);
486 notify_children_of_style_change ((ClutterActor *) self);
487 }
488
489 void
490 st_widget_style_changed (StWidget *widget)
491 {
492 StThemeNode *old_theme_node = NULL;
493
494 widget->priv->is_style_dirty = TRUE;
495 if (widget->priv->theme_node)
496 {
497 old_theme_node = widget->priv->theme_node;
498 widget->priv->theme_node = NULL;
499 }
500
501 /* update the style only if we are mapped */
502 if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR (widget)))
503 st_widget_recompute_style (widget, old_theme_node);
504
505 if (old_theme_node)
506 g_object_unref (old_theme_node);
507 }
508
509 static void
510 on_theme_context_changed (StThemeContext *context,
511 ClutterStage *stage)
512 {
513 notify_children_of_style_change (CLUTTER_ACTOR (stage));
514 }
515
516 static StThemeNode *
517 get_root_theme_node (ClutterStage *stage)
518 {
519 StThemeContext *context = st_theme_context_get_for_stage (stage);
520
521 if (!g_object_get_data (G_OBJECT (context), "st-theme-initialized"))
522 {
523 g_object_set_data (G_OBJECT (context), "st-theme-initialized", GUINT_TO_POINTER (1));
524 g_signal_connect (G_OBJECT (context), "changed",
525 G_CALLBACK (on_theme_context_changed), stage);
526 }
527
528 return st_theme_context_get_root_node (context);
529 }
530
531 /**
532 * st_widget_get_theme_node:
533 * @widget: a #StWidget
534 *
535 * Gets the theme node holding style information for the widget.
536 * The theme node is used to access standard and custom CSS
537 * properties of the widget.
538 *
539 * Note: it is a fatal error to call this on a widget that is
540 * not been added to a stage.
541 *
542 * Return value: (transfer none): the theme node for the widget.
543 * This is owned by the widget. When attributes of the widget
544 * or the environment that affect the styling change (for example
545 * the style_class property of the widget), it will be recreated,
546 * and the ::style-changed signal will be emitted on the widget.
547 */
548 StThemeNode *
549 st_widget_get_theme_node (StWidget *widget)
550 {
551 StWidgetPrivate *priv = widget->priv;
552
553 if (priv->theme_node == NULL)
554 {
555 StThemeNode *parent_node = NULL;
556 ClutterStage *stage = NULL;
557 ClutterActor *parent;
558 char *pseudo_class, *direction_pseudo_class;
559
560 parent = clutter_actor_get_parent (CLUTTER_ACTOR (widget));
561 while (parent != NULL)
562 {
563 if (parent_node == NULL && ST_IS_WIDGET (parent))
564 parent_node = st_widget_get_theme_node (ST_WIDGET (parent));
565 else if (CLUTTER_IS_STAGE (parent))
566 stage = CLUTTER_STAGE (parent);
567
568 parent = clutter_actor_get_parent (parent);
569 }
570
571 if (stage == NULL)
572 {
573 g_error ("st_widget_get_theme_node called on the widget %s which is not in the stage.",
574 st_describe_actor (CLUTTER_ACTOR (widget)));
575 }
576
577 if (parent_node == NULL)
578 parent_node = get_root_theme_node (CLUTTER_STAGE (stage));
579
580 /* Always append a "magic" pseudo class indicating the text
581 * direction, to allow to adapt the CSS when necessary without
582 * requiring separate style sheets.
583 */
584 if (clutter_actor_get_text_direction (CLUTTER_ACTOR (widget)) == CLUTTER_TEXT_DIRECTION_RTL)
585 direction_pseudo_class = "rtl";
586 else
587 direction_pseudo_class = "ltr";
588
589 if (priv->pseudo_class)
590 pseudo_class = g_strconcat(priv->pseudo_class, " ",
591 direction_pseudo_class, NULL);
592 else
593 pseudo_class = direction_pseudo_class;
594
595 priv->theme_node = st_theme_node_new (st_theme_context_get_for_stage (stage),
596 parent_node, priv->theme,
597 G_OBJECT_TYPE (widget),
598 clutter_actor_get_name (CLUTTER_ACTOR (widget)),
599 priv->style_class,
600 pseudo_class,
601 priv->inline_style);
602
603 if (pseudo_class != direction_pseudo_class)
604 g_free (pseudo_class);
605 }
606
607 return priv->theme_node;
608 }
609
610 /**
611 * st_widget_peek_theme_node:
612 * @widget: a #StWidget
613 *
614 * Returns the theme node for the widget if it has already been
615 * computed, %NULL if the widget hasn't been added to a stage or the theme
616 * node hasn't been computed. If %NULL is returned, then ::style-changed
617 * will be reliably emitted before the widget is allocated or painted.
618 *
619 * Return value: (transfer none): the theme node for the widget.
620 * This is owned by the widget. When attributes of the widget
621 * or the environment that affect the styling change (for example
622 * the style_class property of the widget), it will be recreated,
623 * and the ::style-changed signal will be emitted on the widget.
624 */
625 StThemeNode *
626 st_widget_peek_theme_node (StWidget *widget)
627 {
628 StWidgetPrivate *priv = widget->priv;
629
630 return priv->theme_node;
631 }
632
633 static gboolean
634 st_widget_enter (ClutterActor *actor,
635 ClutterCrossingEvent *event)
636 {
637 StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
638
639 if (priv->track_hover)
640 {
641 if (clutter_actor_contains (actor, event->source))
642 st_widget_set_hover (ST_WIDGET (actor), TRUE);
643 else
644 {
645 /* The widget has a grab and is being told about an
646 * enter-event outside its hierarchy. Hopefully we already
647 * got a leave-event, but if not, handle it now.
648 */
649 st_widget_set_hover (ST_WIDGET (actor), FALSE);
650 }
651 }
652
653 if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event)
654 return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event (actor, event);
655 else
656 return FALSE;
657 }
658
659 static gboolean
660 st_widget_leave (ClutterActor *actor,
661 ClutterCrossingEvent *event)
662 {
663 StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
664
665 if (priv->track_hover)
666 {
667 if (!event->related || !clutter_actor_contains (actor, event->related))
668 st_widget_set_hover (ST_WIDGET (actor), FALSE);
669 }
670
671 if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event)
672 return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event (actor, event);
673 else
674 return FALSE;
675 }
676
677 static void
678 st_widget_key_focus_in (ClutterActor *actor)
679 {
680 StWidget *widget = ST_WIDGET (actor);
681
682 st_widget_add_style_pseudo_class (widget, "focus");
683 }
684
685 static void
686 st_widget_key_focus_out (ClutterActor *actor)
687 {
688 StWidget *widget = ST_WIDGET (actor);
689
690 st_widget_remove_style_pseudo_class (widget, "focus");
691 }
692
693 static gboolean
694 st_widget_key_press_event (ClutterActor *actor,
695 ClutterKeyEvent *event)
696 {
697 if (event->keyval == CLUTTER_KEY_Menu ||
698 (event->keyval == CLUTTER_KEY_F10 &&
699 (event->modifier_state & CLUTTER_SHIFT_MASK)))
700 {
701 g_signal_emit (actor, signals[POPUP_MENU], 0);
702 return TRUE;
703 }
704
705 return FALSE;
706 }
707
708 static gboolean
709 st_widget_get_paint_volume (ClutterActor *self,
710 ClutterPaintVolume *volume)
711 {
712 ClutterActorBox paint_box, alloc_box;
713 StThemeNode *theme_node;
714 StWidgetPrivate *priv;
715 ClutterVertex origin;
716
717 /* Setting the paint volume does not make sense when we don't have any allocation */
718 if (!clutter_actor_has_allocation (self))
719 return FALSE;
720
721 priv = ST_WIDGET (self)->priv;
722
723 theme_node = st_widget_get_theme_node (ST_WIDGET (self));
724 clutter_actor_get_allocation_box (self, &alloc_box);
725
726 if (priv->transition_animation)
727 st_theme_node_transition_get_paint_box (priv->transition_animation,
728 &alloc_box, &paint_box);
729 else
730 st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box);
731
732 origin.x = paint_box.x1 - alloc_box.x1;
733 origin.y = paint_box.y1 - alloc_box.y1;
734 origin.z = 0.0f;
735
736 clutter_paint_volume_set_origin (volume, &origin);
737 clutter_paint_volume_set_width (volume, paint_box.x2 - paint_box.x1);
738 clutter_paint_volume_set_height (volume, paint_box.y2 - paint_box.y1);
739
740 if (!clutter_actor_get_clip_to_allocation (self))
741 {
742 ClutterActor *child;
743 /* Based on ClutterGroup/ClutterBox; include the children's
744 * paint volumes, since they may paint outside our allocation.
745 */
746 for (child = clutter_actor_get_first_child (self);
747 child != NULL;
748 child = clutter_actor_get_next_sibling (child))
749 {
750 const ClutterPaintVolume *child_volume;
751
752 child_volume = clutter_actor_get_transformed_paint_volume (child, self);
753 if (!child_volume)
754 return FALSE;
755
756 clutter_paint_volume_union (volume, child_volume);
757 }
758 }
759
760 return TRUE;
761 }
762
763 static GList *
764 st_widget_real_get_focus_chain (StWidget *widget)
765 {
766 return clutter_actor_get_children (CLUTTER_ACTOR (widget));
767 }
768
769
770 static void
771 st_widget_class_init (StWidgetClass *klass)
772 {
773 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
774 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
775 GParamSpec *pspec;
776
777 g_type_class_add_private (klass, sizeof (StWidgetPrivate));
778
779 gobject_class->set_property = st_widget_set_property;
780 gobject_class->get_property = st_widget_get_property;
781 gobject_class->dispose = st_widget_dispose;
782 gobject_class->finalize = st_widget_finalize;
783
784 actor_class->get_preferred_width = st_widget_get_preferred_width;
785 actor_class->get_preferred_height = st_widget_get_preferred_height;
786 actor_class->allocate = st_widget_allocate;
787 actor_class->paint = st_widget_paint;
788 actor_class->get_paint_volume = st_widget_get_paint_volume;
789 actor_class->parent_set = st_widget_parent_set;
790 actor_class->map = st_widget_map;
791 actor_class->unmap = st_widget_unmap;
792
793 actor_class->enter_event = st_widget_enter;
794 actor_class->leave_event = st_widget_leave;
795 actor_class->key_focus_in = st_widget_key_focus_in;
796 actor_class->key_focus_out = st_widget_key_focus_out;
797 actor_class->key_press_event = st_widget_key_press_event;
798
799 actor_class->get_accessible = st_widget_get_accessible;
800
801 klass->style_changed = st_widget_real_style_changed;
802 klass->navigate_focus = st_widget_real_navigate_focus;
803 klass->get_accessible_type = st_widget_accessible_get_type;
804 klass->get_focus_chain = st_widget_real_get_focus_chain;
805
806 /**
807 * StWidget:pseudo-class:
808 *
809 * The pseudo-class of the actor. Typical values include "hover", "active",
810 * "focus".
811 */
812 g_object_class_install_property (gobject_class,
813 PROP_PSEUDO_CLASS,
814 g_param_spec_string ("pseudo-class",
815 "Pseudo Class",
816 "Pseudo class for styling",
817 "",
818 ST_PARAM_READWRITE));
819 /**
820 * StWidget:style-class:
821 *
822 * The style-class of the actor for use in styling.
823 */
824 g_object_class_install_property (gobject_class,
825 PROP_STYLE_CLASS,
826 g_param_spec_string ("style-class",
827 "Style Class",
828 "Style class for styling",
829 "",
830 ST_PARAM_READWRITE));
831
832 /**
833 * StWidget:style:
834 *
835 * Inline style information for the actor as a ';'-separated list of
836 * CSS properties.
837 */
838 g_object_class_install_property (gobject_class,
839 PROP_STYLE,
840 g_param_spec_string ("style",
841 "Style",
842 "Inline style string",
843 "",
844 ST_PARAM_READWRITE));
845
846 /**
847 * StWidget:theme:
848 *
849 * A theme set on this actor overriding the global theming for this actor
850 * and its descendants
851 */
852 g_object_class_install_property (gobject_class,
853 PROP_THEME,
854 g_param_spec_object ("theme",
855 "Theme",
856 "Theme override",
857 ST_TYPE_THEME,
858 ST_PARAM_READWRITE));
859
860 /**
861 * StWidget:stylable:
862 *
863 * Enable or disable styling of the widget
864 */
865 pspec = g_param_spec_boolean ("stylable",
866 "Stylable",
867 "Whether the table should be styled",
868 TRUE,
869 ST_PARAM_READWRITE);
870 g_object_class_install_property (gobject_class,
871 PROP_STYLABLE,
872 pspec);
873
874 /**
875 * StWidget:track-hover:
876 *
877 * Determines whether the widget tracks pointer hover state. If
878 * %TRUE (and the widget is visible and reactive), the
879 * #StWidget:hover property and "hover" style pseudo class will be
880 * adjusted automatically as the pointer moves in and out of the
881 * widget.
882 */
883 pspec = g_param_spec_boolean ("track-hover",
884 "Track hover",
885 "Determines whether the widget tracks hover state",
886 FALSE,
887 ST_PARAM_READWRITE);
888 g_object_class_install_property (gobject_class,
889 PROP_TRACK_HOVER,
890 pspec);
891
892 /**
893 * StWidget:hover:
894 *
895 * Whether or not the pointer is currently hovering over the widget. This is
896 * only tracked automatically if #StWidget:track-hover is %TRUE, but you can
897 * adjust it manually in any case.
898 */
899 pspec = g_param_spec_boolean ("hover",
900 "Hover",
901 "Whether the pointer is hovering over the widget",
902 FALSE,
903 ST_PARAM_READWRITE);
904 g_object_class_install_property (gobject_class,
905 PROP_HOVER,
906 pspec);
907
908 /**
909 * StWidget:can-focus:
910 *
911 * Whether or not the widget can be focused via keyboard navigation.
912 */
913 pspec = g_param_spec_boolean ("can-focus",
914 "Can focus",
915 "Whether the widget can be focused via keyboard navigation",
916 FALSE,
917 ST_PARAM_READWRITE);
918 g_object_class_install_property (gobject_class,
919 PROP_CAN_FOCUS,
920 pspec);
921
922 /**
923 * ClutterActor:label-actor:
924 *
925 * An actor that labels this widget.
926 */
927 g_object_class_install_property (gobject_class,
928 PROP_LABEL_ACTOR,
929 g_param_spec_object ("label-actor",
930 "Label",
931 "Label that identifies this widget",
932 CLUTTER_TYPE_ACTOR,
933 ST_PARAM_READWRITE));
934 /**
935 * StWidget:accessible-role:
936 *
937 * The accessible role of this object
938 */
939 g_object_class_install_property (gobject_class,
940 PROP_ACCESSIBLE_ROLE,
941 g_param_spec_enum ("accessible-role",
942 "Accessible Role",
943 "The accessible role of this object",
944 ATK_TYPE_ROLE,
945 ATK_ROLE_INVALID,
946 G_PARAM_READWRITE));
947
948
949 /**
950 * StWidget:accessible-name:
951 *
952 * Object instance's name for assistive technology access.
953 */
954 g_object_class_install_property (gobject_class,
955 PROP_ACCESSIBLE_NAME,
956 g_param_spec_string ("accessible-name",
957 "Accessible name",
958 "Object instance's name for assistive technology access.",
959 NULL,
960 ST_PARAM_READWRITE));
961
962 /**
963 * StWidget::style-changed:
964 * @widget: the #StWidget
965 *
966 * Emitted when the style information that the widget derives from the
967 * theme changes
968 */
969 signals[STYLE_CHANGED] =
970 g_signal_new ("style-changed",
971 G_TYPE_FROM_CLASS (klass),
972 G_SIGNAL_RUN_LAST,
973 G_STRUCT_OFFSET (StWidgetClass, style_changed),
974 NULL, NULL, NULL,
975 G_TYPE_NONE, 0);
976
977 /**
978 * StWidget::popup-menu:
979 * @widget: the #StWidget
980 *
981 * Emitted when the user has requested a context menu (eg, via a
982 * keybinding)
983 */
984 signals[POPUP_MENU] =
985 g_signal_new ("popup-menu",
986 G_TYPE_FROM_CLASS (klass),
987 G_SIGNAL_RUN_LAST,
988 G_STRUCT_OFFSET (StWidgetClass, popup_menu),
989 NULL, NULL, NULL,
990 G_TYPE_NONE, 0);
991 }
992
993 /**
994 * st_widget_set_theme:
995 * @actor: a #StWidget
996 * @theme: a new style class string
997 *
998 * Overrides the theme that would be inherited from the actor's parent
999 * or the stage with an entirely new theme (set of stylesheets).
1000 */
1001 void
1002 st_widget_set_theme (StWidget *actor,
1003 StTheme *theme)
1004 {
1005 StWidgetPrivate *priv;
1006
1007 g_return_if_fail (ST_IS_WIDGET (actor));
1008
1009 priv = actor->priv;
1010
1011 if (theme != priv->theme)
1012 {
1013 if (priv->theme)
1014 g_object_unref (priv->theme);
1015 priv->theme = g_object_ref (theme);
1016
1017 st_widget_style_changed (actor);
1018
1019 g_object_notify (G_OBJECT (actor), "theme");
1020 }
1021 }
1022
1023 /**
1024 * st_widget_get_theme:
1025 * @actor: a #StWidget
1026 *
1027 * Gets the overriding theme set on the actor. See st_widget_set_theme()
1028 *
1029 * Return value: (transfer none): the overriding theme, or %NULL
1030 */
1031 StTheme *
1032 st_widget_get_theme (StWidget *actor)
1033 {
1034 g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
1035
1036 return actor->priv->theme;
1037 }
1038
1039 static const gchar *
1040 find_class_name (const gchar *class_list,
1041 const gchar *class_name)
1042 {
1043 gint len = strlen (class_name);
1044 const gchar *match;
1045
1046 if (!class_list)
1047 return NULL;
1048
1049 for (match = strstr (class_list, class_name); match; match = strstr (match + 1, class_name))
1050 {
1051 if ((match == class_list || g_ascii_isspace (match[-1])) &&
1052 (match[len] == '\0' || g_ascii_isspace (match[len])))
1053 return match;
1054 }
1055
1056 return NULL;
1057 }
1058
1059 static gboolean
1060 set_class_list (gchar **class_list,
1061 const gchar *new_class_list)
1062 {
1063 if (g_strcmp0 (*class_list, new_class_list) != 0)
1064 {
1065 g_free (*class_list);
1066 *class_list = g_strdup (new_class_list);
1067 return TRUE;
1068 }
1069 else
1070 return FALSE;
1071 }
1072
1073 static gboolean
1074 add_class_name (gchar **class_list,
1075 const gchar *class_name)
1076 {
1077 gchar *new_class_list;
1078
1079 if (*class_list)
1080 {
1081 if (find_class_name (*class_list, class_name))
1082 return FALSE;
1083
1084 new_class_list = g_strdup_printf ("%s %s", *class_list, class_name);
1085 g_free (*class_list);
1086 *class_list = new_class_list;
1087 }
1088 else
1089 *class_list = g_strdup (class_name);
1090
1091 return TRUE;
1092 }
1093
1094 static gboolean
1095 remove_class_name (gchar **class_list,
1096 const gchar *class_name)
1097 {
1098 const gchar *match, *end;
1099 gchar *new_class_list;
1100
1101 if (!*class_list)
1102 return FALSE;
1103
1104 if (strcmp (*class_list, class_name) == 0)
1105 {
1106 g_free (*class_list);
1107 *class_list = NULL;
1108 return TRUE;
1109 }
1110
1111 match = find_class_name (*class_list, class_name);
1112 if (!match)
1113 return FALSE;
1114 end = match + strlen (class_name);
1115
1116 /* Adjust either match or end to include a space as well.
1117 * (One or the other must be possible at this point.)
1118 */
1119 if (match != *class_list)
1120 match--;
1121 else
1122 end++;
1123
1124 new_class_list = g_strdup_printf ("%.*s%s", (int)(match - *class_list),
1125 *class_list, end);
1126 g_free (*class_list);
1127 *class_list = new_class_list;
1128
1129 return TRUE;
1130 }
1131
1132 /**
1133 * st_widget_set_style_class_name:
1134 * @actor: a #StWidget
1135 * @style_class_list: (allow-none): a new style class list string
1136 *
1137 * Set the style class name list. @style_class_list can either be
1138 * %NULL, for no classes, or a space-separated list of style class
1139 * names. See also st_widget_add_style_class_name() and
1140 * st_widget_remove_style_class_name().
1141 */
1142 void
1143 st_widget_set_style_class_name (StWidget *actor,
1144 const gchar *style_class_list)
1145 {
1146 g_return_if_fail (ST_IS_WIDGET (actor));
1147
1148 if (set_class_list (&actor->priv->style_class, style_class_list))
1149 {
1150 st_widget_style_changed (actor);
1151 g_object_notify (G_OBJECT (actor), "style-class");
1152 }
1153 }
1154
1155 /**
1156 * st_widget_add_style_class_name:
1157 * @actor: a #StWidget
1158 * @style_class: a style class name string
1159 *
1160 * Adds @style_class to @actor's style class name list, if it is not
1161 * already present.
1162 */
1163 void
1164 st_widget_add_style_class_name (StWidget *actor,
1165 const gchar *style_class)
1166 {
1167 g_return_if_fail (ST_IS_WIDGET (actor));
1168 g_return_if_fail (style_class != NULL);
1169
1170 if (add_class_name (&actor->priv->style_class, style_class))
1171 {
1172 st_widget_style_changed (actor);
1173 g_object_notify (G_OBJECT (actor), "style-class");
1174 }
1175 }
1176
1177 /**
1178 * st_widget_remove_style_class_name:
1179 * @actor: a #StWidget
1180 * @style_class: a style class name string
1181 *
1182 * Removes @style_class from @actor's style class name, if it is
1183 * present.
1184 */
1185 void
1186 st_widget_remove_style_class_name (StWidget *actor,
1187 const gchar *style_class)
1188 {
1189 g_return_if_fail (ST_IS_WIDGET (actor));
1190 g_return_if_fail (style_class != NULL);
1191
1192 if (remove_class_name (&actor->priv->style_class, style_class))
1193 {
1194 st_widget_style_changed (actor);
1195 g_object_notify (G_OBJECT (actor), "style-class");
1196 }
1197 }
1198
1199 /**
1200 * st_widget_get_style_class_name:
1201 * @actor: a #StWidget
1202 *
1203 * Get the current style class name
1204 *
1205 * Returns: the class name string. The string is owned by the #StWidget and
1206 * should not be modified or freed.
1207 */
1208 const gchar*
1209 st_widget_get_style_class_name (StWidget *actor)
1210 {
1211 g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
1212
1213 return actor->priv->style_class;
1214 }
1215
1216 /**
1217 * st_widget_has_style_class_name:
1218 * @actor: a #StWidget
1219 * @style_class: a style class string
1220 *
1221 * Tests if @actor's style class list includes @style_class.
1222 *
1223 * Returns: whether or not @actor's style class list includes
1224 * @style_class.
1225 */
1226 gboolean
1227 st_widget_has_style_class_name (StWidget *actor,
1228 const gchar *style_class)
1229 {
1230 g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
1231
1232 return find_class_name (actor->priv->style_class, style_class) != NULL;
1233 }
1234
1235 /**
1236 * st_widget_get_style_pseudo_class:
1237 * @actor: a #StWidget
1238 *
1239 * Get the current style pseudo class list.
1240 *
1241 * Note that an actor can have multiple pseudo classes; if you just
1242 * want to test for the presence of a specific pseudo class, use
1243 * st_widget_has_style_pseudo_class().
1244 *
1245 * Returns: the pseudo class list string. The string is owned by the
1246 * #StWidget and should not be modified or freed.
1247 */
1248 const gchar*
1249 st_widget_get_style_pseudo_class (StWidget *actor)
1250 {
1251 g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
1252
1253 return actor->priv->pseudo_class;
1254 }
1255
1256 /**
1257 * st_widget_has_style_pseudo_class:
1258 * @actor: a #StWidget
1259 * @pseudo_class: a pseudo class string
1260 *
1261 * Tests if @actor's pseudo class list includes @pseudo_class.
1262 *
1263 * Returns: whether or not @actor's pseudo class list includes
1264 * @pseudo_class.
1265 */
1266 gboolean
1267 st_widget_has_style_pseudo_class (StWidget *actor,
1268 const gchar *pseudo_class)
1269 {
1270 g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
1271
1272 return find_class_name (actor->priv->pseudo_class, pseudo_class) != NULL;
1273 }
1274
1275 /**
1276 * st_widget_set_style_pseudo_class:
1277 * @actor: a #StWidget
1278 * @pseudo_class_list: (allow-none): a new pseudo class list string
1279 *
1280 * Set the style pseudo class list. @pseudo_class_list can either be
1281 * %NULL, for no classes, or a space-separated list of pseudo class
1282 * names. See also st_widget_add_style_pseudo_class() and
1283 * st_widget_remove_style_pseudo_class().
1284 */
1285 void
1286 st_widget_set_style_pseudo_class (StWidget *actor,
1287 const gchar *pseudo_class_list)
1288 {
1289 g_return_if_fail (ST_IS_WIDGET (actor));
1290
1291 if (set_class_list (&actor->priv->pseudo_class, pseudo_class_list))
1292 {
1293 st_widget_style_changed (actor);
1294 g_object_notify (G_OBJECT (actor), "pseudo-class");
1295 }
1296 }
1297
1298 /**
1299 * st_widget_add_style_pseudo_class:
1300 * @actor: a #StWidget
1301 * @pseudo_class: a pseudo class string
1302 *
1303 * Adds @pseudo_class to @actor's pseudo class list, if it is not
1304 * already present.
1305 */
1306 void
1307 st_widget_add_style_pseudo_class (StWidget *actor,
1308 const gchar *pseudo_class)
1309 {
1310 g_return_if_fail (ST_IS_WIDGET (actor));
1311 g_return_if_fail (pseudo_class != NULL);
1312
1313 if (add_class_name (&actor->priv->pseudo_class, pseudo_class))
1314 {
1315 st_widget_style_changed (actor);
1316 g_object_notify (G_OBJECT (actor), "pseudo-class");
1317 }
1318 }
1319
1320 /**
1321 * st_widget_remove_style_pseudo_class:
1322 * @actor: a #StWidget
1323 * @pseudo_class: a pseudo class string
1324 *
1325 * Removes @pseudo_class from @actor's pseudo class, if it is present.
1326 */
1327 void
1328 st_widget_remove_style_pseudo_class (StWidget *actor,
1329 const gchar *pseudo_class)
1330 {
1331 g_return_if_fail (ST_IS_WIDGET (actor));
1332 g_return_if_fail (pseudo_class != NULL);
1333
1334 if (remove_class_name (&actor->priv->pseudo_class, pseudo_class))
1335 {
1336 st_widget_style_changed (actor);
1337 g_object_notify (G_OBJECT (actor), "pseudo-class");
1338 }
1339 }
1340
1341 /**
1342 * st_widget_set_style:
1343 * @actor: a #StWidget
1344 * @style: (allow-none): a inline style string, or %NULL
1345 *
1346 * Set the inline style string for this widget. The inline style string is an
1347 * optional ';'-separated list of CSS properties that override the style as
1348 * determined from the stylesheets of the current theme.
1349 */
1350 void
1351 st_widget_set_style (StWidget *actor,
1352 const gchar *style)
1353 {
1354 StWidgetPrivate *priv;
1355
1356 g_return_if_fail (ST_IS_WIDGET (actor));
1357
1358 priv = actor->priv;
1359
1360 if (g_strcmp0 (style, priv->inline_style))
1361 {
1362 g_free (priv->inline_style);
1363 priv->inline_style = g_strdup (style);
1364
1365 st_widget_style_changed (actor);
1366
1367 g_object_notify (G_OBJECT (actor), "style");
1368 }
1369 }
1370
1371 /**
1372 * st_widget_get_style:
1373 * @actor: a #StWidget
1374 *
1375 * Get the current inline style string. See st_widget_set_style().
1376 *
1377 * Returns: The inline style string, or %NULL. The string is owned by the
1378 * #StWidget and should not be modified or freed.
1379 */
1380 const gchar*
1381 st_widget_get_style (StWidget *actor)
1382 {
1383 g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
1384
1385 return actor->priv->inline_style;
1386 }
1387
1388 static void
1389 st_widget_name_notify (StWidget *widget,
1390 GParamSpec *pspec,
1391 gpointer data)
1392 {
1393 st_widget_style_changed (widget);
1394 }
1395
1396 static void
1397 st_widget_reactive_notify (StWidget *widget,
1398 GParamSpec *pspec,
1399 gpointer data)
1400 {
1401 if (clutter_actor_get_reactive (CLUTTER_ACTOR (widget)))
1402 st_widget_remove_style_pseudo_class (widget, "insensitive");
1403 else
1404 st_widget_add_style_pseudo_class (widget, "insensitive");
1405 }
1406
1407 static void
1408 st_widget_first_child_notify (StWidget *widget,
1409 GParamSpec *pspec,
1410 gpointer data)
1411 {
1412 ClutterActor *first_child;
1413
1414 if (widget->priv->prev_first_child != NULL)
1415 {
1416 st_widget_remove_style_pseudo_class (widget->priv->prev_first_child, "first-child");
1417 g_clear_object (&widget->priv->prev_first_child);
1418 }
1419
1420 first_child = clutter_actor_get_first_child (CLUTTER_ACTOR (widget));
1421
1422 if (first_child == NULL)
1423 return;
1424
1425 if (ST_IS_WIDGET (first_child))
1426 {
1427 st_widget_add_style_pseudo_class (ST_WIDGET (first_child), "first-child");
1428 widget->priv->prev_first_child = g_object_ref (ST_WIDGET (first_child));
1429 }
1430 }
1431
1432 static void
1433 st_widget_last_child_notify (StWidget *widget,
1434 GParamSpec *pspec,
1435 gpointer data)
1436 {
1437 ClutterActor *last_child;
1438
1439 if (widget->priv->prev_last_child != NULL)
1440 {
1441 st_widget_remove_style_pseudo_class (widget->priv->prev_last_child, "last-child");
1442 g_clear_object (&widget->priv->prev_last_child);
1443 }
1444
1445 last_child = clutter_actor_get_last_child (CLUTTER_ACTOR (widget));
1446
1447 if (last_child == NULL)
1448 return;
1449
1450 if (ST_IS_WIDGET (last_child))
1451 {
1452 st_widget_add_style_pseudo_class (ST_WIDGET (last_child), "last-child");
1453 widget->priv->prev_last_child = g_object_ref (ST_WIDGET (last_child));
1454 }
1455 }
1456
1457 static void
1458 st_widget_init (StWidget *actor)
1459 {
1460 StWidgetPrivate *priv;
1461
1462 actor->priv = priv = ST_WIDGET_GET_PRIVATE (actor);
1463 priv->is_stylable = TRUE;
1464 priv->transition_animation = NULL;
1465 priv->local_state_set = atk_state_set_new ();
1466
1467 /* connect style changed */
1468 g_signal_connect (actor, "notify::name", G_CALLBACK (st_widget_name_notify), NULL);
1469 g_signal_connect (actor, "notify::reactive", G_CALLBACK (st_widget_reactive_notify), NULL);
1470
1471 g_signal_connect (actor, "notify::first-child", G_CALLBACK (st_widget_first_child_notify), NULL);
1472 g_signal_connect (actor, "notify::last-child", G_CALLBACK (st_widget_last_child_notify), NULL);
1473 }
1474
1475 static void
1476 on_transition_completed (StThemeNodeTransition *transition,
1477 StWidget *widget)
1478 {
1479 st_widget_remove_transition (widget);
1480 }
1481
1482 static void
1483 st_widget_recompute_style (StWidget *widget,
1484 StThemeNode *old_theme_node)
1485 {
1486 StThemeNode *new_theme_node = st_widget_get_theme_node (widget);
1487 int transition_duration;
1488 gboolean paint_equal;
1489
1490 if (!old_theme_node ||
1491 !st_theme_node_geometry_equal (old_theme_node, new_theme_node))
1492 clutter_actor_queue_relayout ((ClutterActor *) widget);
1493
1494 transition_duration = st_theme_node_get_transition_duration (new_theme_node);
1495
1496 paint_equal = old_theme_node && st_theme_node_paint_equal (old_theme_node, new_theme_node);
1497
1498 if (paint_equal)
1499 st_theme_node_copy_cached_paint_state (new_theme_node, old_theme_node);
1500
1501 if (transition_duration > 0)
1502 {
1503 if (widget->priv->transition_animation != NULL)
1504 {
1505 st_theme_node_transition_update (widget->priv->transition_animation,
1506 new_theme_node);
1507 }
1508 else if (old_theme_node && !paint_equal)
1509 {
1510 /* Since our transitions are only of the painting done by StThemeNode, we
1511 * only want to start a transition when what is painted changes; if
1512 * other visual aspects like the foreground color of a label change,
1513 * we can't animate that anyways.
1514 */
1515
1516 widget->priv->transition_animation =
1517 st_theme_node_transition_new (old_theme_node,
1518 new_theme_node,
1519 transition_duration);
1520
1521 g_signal_connect (widget->priv->transition_animation, "completed",
1522 G_CALLBACK (on_transition_completed), widget);
1523 g_signal_connect_swapped (widget->priv->transition_animation,
1524 "new-frame",
1525 G_CALLBACK (clutter_actor_queue_redraw),
1526 widget);
1527 }
1528 }
1529 else if (widget->priv->transition_animation)
1530 {
1531 st_widget_remove_transition (widget);
1532 }
1533
1534 g_signal_emit (widget, signals[STYLE_CHANGED], 0);
1535 widget->priv->is_style_dirty = FALSE;
1536 }
1537
1538 /**
1539 * st_widget_ensure_style:
1540 * @widget: A #StWidget
1541 *
1542 * Ensures that @widget has read its style information.
1543 *
1544 */
1545 void
1546 st_widget_ensure_style (StWidget *widget)
1547 {
1548 g_return_if_fail (ST_IS_WIDGET (widget));
1549
1550 if (widget->priv->is_style_dirty)
1551 st_widget_recompute_style (widget, NULL);
1552 }
1553
1554 /**
1555 * st_widget_set_track_hover:
1556 * @widget: A #StWidget
1557 * @track_hover: %TRUE if the widget should track the pointer hover state
1558 *
1559 * Enables hover tracking on the #StWidget.
1560 *
1561 * If hover tracking is enabled, and the widget is visible and
1562 * reactive, then @widget's #StWidget:hover property will be updated
1563 * automatically to reflect whether the pointer is in @widget (or one
1564 * of its children), and @widget's #StWidget:pseudo-class will have
1565 * the "hover" class added and removed from it accordingly.
1566 *
1567 * Note that currently it is not possible to correctly track the hover
1568 * state when another actor has a pointer grab. You can use
1569 * st_widget_sync_hover() to update the property manually in this
1570 * case.
1571 */
1572 void
1573 st_widget_set_track_hover (StWidget *widget,
1574 gboolean track_hover)
1575 {
1576 StWidgetPrivate *priv;
1577
1578 g_return_if_fail (ST_IS_WIDGET (widget));
1579
1580 priv = widget->priv;
1581
1582 if (priv->track_hover != track_hover)
1583 {
1584 priv->track_hover = track_hover;
1585 g_object_notify (G_OBJECT (widget), "track-hover");
1586
1587 if (priv->track_hover)
1588 st_widget_sync_hover (widget);
1589 else
1590 st_widget_set_hover (widget, FALSE);
1591 }
1592 }
1593
1594 /**
1595 * st_widget_get_track_hover:
1596 * @widget: A #StWidget
1597 *
1598 * Returns the current value of the track-hover property. See
1599 * st_widget_set_track_hover() for more information.
1600 *
1601 * Returns: current value of track-hover on @widget
1602 */
1603 gboolean
1604 st_widget_get_track_hover (StWidget *widget)
1605 {
1606 g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
1607
1608 return widget->priv->track_hover;
1609 }
1610
1611 /**
1612 * st_widget_set_hover:
1613 * @widget: A #StWidget
1614 * @hover: whether the pointer is hovering over the widget
1615 *
1616 * Sets @widget's hover property and adds or removes "hover" from its
1617 * pseudo class accordingly.
1618 *
1619 * If you have set #StWidget:track-hover, you should not need to call
1620 * this directly. You can call st_widget_sync_hover() if the hover
1621 * state might be out of sync due to another actor's pointer grab.
1622 */
1623 void
1624 st_widget_set_hover (StWidget *widget,
1625 gboolean hover)
1626 {
1627 StWidgetPrivate *priv;
1628
1629 g_return_if_fail (ST_IS_WIDGET (widget));
1630
1631 priv = widget->priv;
1632
1633 if (priv->hover != hover)
1634 {
1635 priv->hover = hover;
1636 if (priv->hover)
1637 st_widget_add_style_pseudo_class (widget, "hover");
1638 else
1639 st_widget_remove_style_pseudo_class (widget, "hover");
1640 g_object_notify (G_OBJECT (widget), "hover");
1641 }
1642 }
1643
1644 /**
1645 * st_widget_sync_hover:
1646 * @widget: A #StWidget
1647 *
1648 * Sets @widget's hover state according to the current pointer
1649 * position. This can be used to ensure that it is correct after
1650 * (or during) a pointer grab.
1651 */
1652 void
1653 st_widget_sync_hover (StWidget *widget)
1654 {
1655 ClutterDeviceManager *device_manager;
1656 ClutterInputDevice *pointer;
1657 ClutterActor *pointer_actor;
1658
1659 device_manager = clutter_device_manager_get_default ();
1660 pointer = clutter_device_manager_get_core_device (device_manager,
1661 CLUTTER_POINTER_DEVICE);
1662 pointer_actor = clutter_input_device_get_pointer_actor (pointer);
1663 if (pointer_actor)
1664 st_widget_set_hover (widget, clutter_actor_contains (CLUTTER_ACTOR (widget), pointer_actor));
1665 else
1666 st_widget_set_hover (widget, FALSE);
1667 }
1668
1669 /**
1670 * st_widget_get_hover:
1671 * @widget: A #StWidget
1672 *
1673 * If #StWidget:track-hover is set, this returns whether the pointer
1674 * is currently over the widget.
1675 *
1676 * Returns: current value of hover on @widget
1677 */
1678 gboolean
1679 st_widget_get_hover (StWidget *widget)
1680 {
1681 g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
1682
1683 return widget->priv->hover;
1684 }
1685
1686 /**
1687 * st_widget_set_can_focus:
1688 * @widget: A #StWidget
1689 * @can_focus: %TRUE if the widget can receive keyboard focus
1690 * via keyboard navigation
1691 *
1692 * Marks @widget as being able to receive keyboard focus via
1693 * keyboard navigation.
1694 */
1695 void
1696 st_widget_set_can_focus (StWidget *widget,
1697 gboolean can_focus)
1698 {
1699 StWidgetPrivate *priv;
1700
1701 g_return_if_fail (ST_IS_WIDGET (widget));
1702
1703 priv = widget->priv;
1704
1705 if (priv->can_focus != can_focus)
1706 {
1707 priv->can_focus = can_focus;
1708 g_object_notify (G_OBJECT (widget), "can-focus");
1709 }
1710 }
1711
1712 /**
1713 * st_widget_get_can_focus:
1714 * @widget: A #StWidget
1715 *
1716 * Returns the current value of the can-focus property. See
1717 * st_widget_set_can_focus() for more information.
1718 *
1719 * Returns: current value of can-focus on @widget
1720 */
1721 gboolean
1722 st_widget_get_can_focus (StWidget *widget)
1723 {
1724 g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
1725
1726 return widget->priv->can_focus;
1727 }
1728
1729 /* filter @children to contain only only actors that overlap @rbox
1730 * when moving in @direction. (Assuming no transformations.)
1731 */
1732 static GList *
1733 filter_by_position (GList *children,
1734 ClutterActorBox *rbox,
1735 GtkDirectionType direction)
1736 {
1737 ClutterActorBox cbox;
1738 ClutterVertex abs_vertices[4];
1739 GList *l, *ret;
1740 ClutterActor *child;
1741
1742 for (l = children, ret = NULL; l; l = l->next)
1743 {
1744 child = l->data;
1745 clutter_actor_get_abs_allocation_vertices (child, abs_vertices);
1746 clutter_actor_box_from_vertices (&cbox, abs_vertices);
1747
1748 /* Filter out children if they are in the wrong direction from
1749 * @rbox, or if they don't overlap it. To account for floating-
1750 * point imprecision, an actor is "down" (etc.) from an another
1751 * actor even if it overlaps it by up to 0.1 pixels.
1752 */
1753 switch (direction)
1754 {
1755 case GTK_DIR_UP:
1756 if (cbox.y2 > rbox->y1 + 0.1)
1757 continue;
1758 break;
1759
1760 case GTK_DIR_DOWN:
1761 if (cbox.y1 < rbox->y2 - 0.1)
1762 continue;
1763 break;
1764
1765 case GTK_DIR_LEFT:
1766 if (cbox.x2 > rbox->x1 + 0.1)
1767 continue;
1768 break;
1769
1770 case GTK_DIR_RIGHT:
1771 if (cbox.x1 < rbox->x2 - 0.1)
1772 continue;
1773 break;
1774
1775 default:
1776 g_return_val_if_reached (NULL);
1777 }
1778
1779 ret = g_list_prepend (ret, child);
1780 }
1781
1782 g_list_free (children);
1783 return ret;
1784 }
1785
1786
1787 typedef struct {
1788 GtkDirectionType direction;
1789 ClutterActorBox box;
1790 } StWidgetChildSortData;
1791
1792 static int
1793 sort_by_position (gconstpointer a,
1794 gconstpointer b,
1795 gpointer user_data)
1796 {
1797 ClutterActor *actor_a = (ClutterActor *)a;
1798 ClutterActor *actor_b = (ClutterActor *)b;
1799 StWidgetChildSortData *sort_data = user_data;
1800 GtkDirectionType direction = sort_data->direction;
1801 ClutterActorBox abox, bbox;
1802 ClutterVertex abs_vertices[4];
1803 int ax, ay, bx, by;
1804 int cmp, fmid;
1805
1806 /* Determine the relationship, relative to motion in @direction, of
1807 * the center points of the two actors. Eg, for %GTK_DIR_UP, we
1808 * return a negative number if @actor_a's center is below @actor_b's
1809 * center, and postive if vice versa, which will result in an
1810 * overall list sorted bottom-to-top.
1811 */
1812
1813 clutter_actor_get_abs_allocation_vertices (actor_a, abs_vertices);
1814 clutter_actor_box_from_vertices (&abox, abs_vertices);
1815 ax = (int)(abox.x1 + abox.x2) / 2;
1816 ay = (int)(abox.y1 + abox.y2) / 2;
1817 clutter_actor_get_abs_allocation_vertices (actor_b, abs_vertices);
1818 clutter_actor_box_from_vertices (&bbox, abs_vertices);
1819 bx = (int)(bbox.x1 + bbox.x2) / 2;
1820 by = (int)(bbox.y1 + bbox.y2) / 2;
1821
1822 switch (direction)
1823 {
1824 case GTK_DIR_UP:
1825 cmp = by - ay;
1826 break;
1827 case GTK_DIR_DOWN:
1828 cmp = ay - by;
1829 break;
1830 case GTK_DIR_LEFT:
1831 cmp = bx - ax;
1832 break;
1833 case GTK_DIR_RIGHT:
1834 cmp = ax - bx;
1835 break;
1836 default:
1837 g_return_val_if_reached (0);
1838 }
1839
1840 if (cmp)
1841 return cmp;
1842
1843 /* If two actors have the same center on the axis being sorted,
1844 * prefer the one that is closer to the center of the current focus
1845 * actor on the other axis. Eg, for %GTK_DIR_UP, prefer whichever
1846 * of @actor_a and @actor_b has a horizontal center closest to the
1847 * current focus actor's horizontal center.
1848 *
1849 * (This matches GTK's behavior.)
1850 */
1851 switch (direction)
1852 {
1853 case GTK_DIR_UP:
1854 case GTK_DIR_DOWN:
1855 fmid = (int)(sort_data->box.x1 + sort_data->box.x2) / 2;
1856 return abs (ax - fmid) - abs (bx - fmid);
1857 case GTK_DIR_LEFT:
1858 case GTK_DIR_RIGHT:
1859 fmid = (int)(sort_data->box.y1 + sort_data->box.y2) / 2;
1860 return abs (ay - fmid) - abs (by - fmid);
1861 default:
1862 g_return_val_if_reached (0);
1863 }
1864 }
1865
1866 static gboolean
1867 st_widget_real_navigate_focus (StWidget *widget,
1868 ClutterActor *from,
1869 GtkDirectionType direction)
1870 {
1871 ClutterActor *widget_actor, *focus_child;
1872 GList *children, *l;
1873
1874 widget_actor = CLUTTER_ACTOR (widget);
1875 if (from == widget_actor)
1876 return FALSE;
1877
1878 /* Figure out if @from is a descendant of @widget, and if so,
1879 * set @focus_child to the immediate child of @widget that
1880 * contains (or *is*) @from.
1881 */
1882 focus_child = from;
1883 while (focus_child && clutter_actor_get_parent (focus_child) != widget_actor)
1884 focus_child = clutter_actor_get_parent (focus_child);
1885
1886 if (widget->priv->can_focus)
1887 {
1888 if (!focus_child)
1889 {
1890 if (CLUTTER_ACTOR_IS_MAPPED (widget_actor))
1891 {
1892 /* Accept focus from outside */
1893 clutter_actor_grab_key_focus (widget_actor);
1894 return TRUE;
1895 }
1896 else
1897 {
1898 /* Refuse to set focus on hidden actors */
1899 return FALSE;
1900 }
1901 }
1902 else
1903 {
1904 /* Yield focus from within: since @widget itself is
1905 * focusable we don't allow the focus to be navigated
1906 * within @widget.
1907 */
1908 return FALSE;
1909 }
1910 }
1911
1912 /* See if we can navigate within @focus_child */
1913 if (focus_child && ST_IS_WIDGET (focus_child))
1914 {
1915 if (st_widget_navigate_focus (ST_WIDGET (focus_child), from, direction, FALSE))
1916 return TRUE;
1917 }
1918
1919 children = st_widget_get_focus_chain (widget);
1920 if (direction == GTK_DIR_TAB_FORWARD ||
1921 direction == GTK_DIR_TAB_BACKWARD)
1922 {
1923 /* At this point we know that we want to navigate focus to one of
1924 * @widget's immediate children; the next one after @focus_child, or the
1925 * first one if @focus_child is %NULL. (With "next" and "first" being
1926 * determined by @direction.)
1927 */
1928 if (direction == GTK_DIR_TAB_BACKWARD)
1929 children = g_list_reverse (children);
1930
1931 if (focus_child)
1932 {
1933 /* Remove focus_child and any earlier children */
1934 while (children && children->data != focus_child)
1935 children = g_list_delete_link (children, children);
1936 if (children)
1937 children = g_list_delete_link (children, children);
1938 }
1939 }
1940 else /* direction is an arrow key, not tab */
1941 {
1942 StWidgetChildSortData sort_data;
1943 ClutterVertex abs_vertices[4];
1944
1945 /* Compute the allocation box of the previous focused actor. If there
1946 * was no previous focus, use the coordinates of the appropriate edge of
1947 * @widget.
1948 *
1949 * Note that all of this code assumes the actors are not
1950 * transformed (or at most, they are all scaled by the same
1951 * amount). If @widget or any of its children is rotated, or
1952 * any child is inconsistently scaled, then the focus chain will
1953 * probably be unpredictable.
1954 */
1955 if (from)
1956 {
1957 clutter_actor_get_abs_allocation_vertices (from, abs_vertices);
1958 clutter_actor_box_from_vertices (&sort_data.box, abs_vertices);
1959 }
1960 else
1961 {
1962 clutter_actor_get_abs_allocation_vertices (widget_actor, abs_vertices);
1963 clutter_actor_box_from_vertices (&sort_data.box, abs_vertices);
1964 switch (direction)
1965 {
1966 case GTK_DIR_UP:
1967 sort_data.box.y1 = sort_data.box.y2;
1968 break;
1969 case GTK_DIR_DOWN:
1970 sort_data.box.y2 = sort_data.box.y1;
1971 break;
1972 case GTK_DIR_LEFT:
1973 sort_data.box.x1 = sort_data.box.x2;
1974 break;
1975 case GTK_DIR_RIGHT:
1976 sort_data.box.x2 = sort_data.box.x1;
1977 break;
1978 default:
1979 g_warn_if_reached ();
1980 }
1981 }
1982 sort_data.direction = direction;
1983
1984 if (from)
1985 children = filter_by_position (children, &sort_data.box, direction);
1986 if (children)
1987 children = g_list_sort_with_data (children, sort_by_position, &sort_data);
1988 }
1989
1990 /* Now try each child in turn */
1991 for (l = children; l; l = l->next)
1992 {
1993 if (ST_IS_WIDGET (l->data))
1994 {
1995 if (st_widget_navigate_focus (l->data, from, direction, FALSE))
1996 {
1997 g_list_free (children);
1998 return TRUE;
1999 }
2000 }
2001 }
2002
2003 g_list_free (children);
2004 return FALSE;
2005 }
2006
2007
2008 /**
2009 * st_widget_navigate_focus:
2010 * @widget: the "top level" container
2011 * @from: (allow-none): the actor that the focus is coming from
2012 * @direction: the direction focus is moving in
2013 * @wrap_around: whether focus should wrap around
2014 *
2015 * Tries to update the keyboard focus within @widget in response to a
2016 * keyboard event.
2017 *
2018 * If @from is a descendant of @widget, this attempts to move the
2019 * keyboard focus to the next descendant of @widget (in the order
2020 * implied by @direction) that has the #StWidget:can-focus property
2021 * set. If @from is %NULL, this attempts to focus either @widget
2022 * itself, or its first descendant in the order implied by
2023 * @direction. If @from is outside of @widget, it behaves as if it was
2024 * a descendant if @direction is one of the directional arrows and as
2025 * if it was %NULL otherwise.
2026 *
2027 * If a container type is marked #StWidget:can-focus, the expected
2028 * behavior is that it will only take up a single slot on the focus
2029 * chain as a whole, rather than allowing navigation between its child
2030 * actors (or having a distinction between itself being focused and
2031 * one of its children being focused).
2032 *
2033 * Some widget classes might have slightly different behavior from the
2034 * above, where that would make more sense.
2035 *
2036 * If @wrap_around is %TRUE and @from is a child of @widget, but the
2037 * widget has no further children that can accept the focus in the
2038 * given direction, then st_widget_navigate_focus() will try a second
2039 * time, using a %NULL @from, which should cause it to reset the focus
2040 * to the first available widget in the given direction.
2041 *
2042 * Return value: %TRUE if clutter_actor_grab_key_focus() has been
2043 * called on an actor. %FALSE if not.
2044 */
2045 gboolean
2046 st_widget_navigate_focus (StWidget *widget,
2047 ClutterActor *from,
2048 GtkDirectionType direction,
2049 gboolean wrap_around)
2050 {
2051 g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
2052
2053 if (ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, from, direction))
2054 return TRUE;
2055 if (wrap_around && from && clutter_actor_contains (CLUTTER_ACTOR (widget), from))
2056 return ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, NULL, direction);
2057 return FALSE;
2058 }
2059
2060 static gboolean
2061 append_actor_text (GString *desc,
2062 ClutterActor *actor)
2063 {
2064 if (CLUTTER_IS_TEXT (actor))
2065 {
2066 g_string_append_printf (desc, " (\"%s\")",
2067 clutter_text_get_text (CLUTTER_TEXT (actor)));
2068 return TRUE;
2069 }
2070 else if (ST_IS_LABEL (actor))
2071 {
2072 g_string_append_printf (desc, " (\"%s\")",
2073 st_label_get_text (ST_LABEL (actor)));
2074 return TRUE;
2075 }
2076 else
2077 return FALSE;
2078 }
2079
2080 /**
2081 * st_describe_actor:
2082 * @actor: a #ClutterActor
2083 *
2084 * Creates a string describing @actor, for use in debugging. This
2085 * includes the class name and actor name (if any), plus if @actor
2086 * is an #StWidget, its style class and pseudo class names.
2087 *
2088 * Return value: the debug name.
2089 */
2090 char *
2091 st_describe_actor (ClutterActor *actor)
2092 {
2093 GString *desc;
2094 const char *name;
2095 int i;
2096
2097 if (!actor)
2098 return g_strdup ("[null]");
2099
2100 desc = g_string_new (NULL);
2101 g_string_append_printf (desc, "[%p %s", actor,
2102 G_OBJECT_TYPE_NAME (actor));
2103
2104 if (ST_IS_WIDGET (actor))
2105 {
2106 const char *style_class = st_widget_get_style_class_name (ST_WIDGET (actor));
2107 const char *pseudo_class = st_widget_get_style_pseudo_class (ST_WIDGET (actor));
2108 char **classes;
2109
2110 if (style_class)
2111 {
2112 classes = g_strsplit (style_class, ",", -1);
2113 for (i = 0; classes[i]; i++)
2114 {
2115 g_strchug (classes[i]);
2116 g_string_append_printf (desc, ".%s", classes[i]);
2117 }
2118 g_strfreev (classes);
2119 }
2120
2121 if (pseudo_class)
2122 {
2123 classes = g_strsplit (pseudo_class, ",", -1);
2124 for (i = 0; classes[i]; i++)
2125 {
2126 g_strchug (classes[i]);
2127 g_string_append_printf (desc, ":%s", classes[i]);
2128 }
2129 g_strfreev (classes);
2130 }
2131 }
2132
2133 name = clutter_actor_get_name (actor);
2134 if (name)
2135 g_string_append_printf (desc, " \"%s\"", name);
2136
2137 if (!append_actor_text (desc, actor))
2138 {
2139 GList *children, *l;
2140
2141 /* Do a limited search of @actor's children looking for a label */
2142 children = clutter_actor_get_children (actor);
2143 for (l = children, i = 0; l && i < 20; l = l->next, i++)
2144 {
2145 if (append_actor_text (desc, l->data))
2146 break;
2147 children = g_list_concat (children, clutter_actor_get_children (l->data));
2148 }
2149 g_list_free (children);
2150 }
2151
2152 g_string_append_c (desc, ']');
2153
2154 return g_string_free (desc, FALSE);
2155 }
2156
2157 /**
2158 * st_set_slow_down_factor:
2159 * @factor: new slow-down factor
2160 *
2161 * Set a global factor applied to all animation durations
2162 */
2163 void
2164 st_set_slow_down_factor (gfloat factor)
2165 {
2166 st_slow_down_factor = factor;
2167 }
2168
2169 /**
2170 * st_get_slow_down_factor:
2171 *
2172 * Returns: the global factor applied to all animation durations
2173 */
2174 gfloat
2175 st_get_slow_down_factor ()
2176 {
2177 return st_slow_down_factor;
2178 }
2179
2180
2181 /**
2182 * st_widget_get_label_actor:
2183 * @widget: a #StWidget
2184 *
2185 * Gets the label that identifies @widget if it is defined
2186 *
2187 * Return value: (transfer none): the label that identifies the widget
2188 */
2189 ClutterActor *
2190 st_widget_get_label_actor (StWidget *widget)
2191 {
2192 g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
2193
2194 return widget->priv->label_actor;
2195 }
2196
2197 /**
2198 * st_widget_set_label_actor:
2199 * @widget: a #StWidget
2200 * @label: a #ClutterActor
2201 *
2202 * Sets @label as the #ClutterActor that identifies (labels)
2203 * @widget. @label can be %NULL to indicate that @widget is not
2204 * labelled any more
2205 */
2206
2207 void
2208 st_widget_set_label_actor (StWidget *widget,
2209 ClutterActor *label)
2210 {
2211 g_return_if_fail (ST_IS_WIDGET (widget));
2212
2213 if (widget->priv->label_actor != label)
2214 {
2215 if (widget->priv->label_actor)
2216 g_object_unref (widget->priv->label_actor);
2217
2218 if (label != NULL)
2219 widget->priv->label_actor = g_object_ref (label);
2220 else
2221 widget->priv->label_actor = NULL;
2222
2223 g_object_notify (G_OBJECT (widget), "label-actor");
2224 }
2225 }
2226
2227 /**
2228 * st_widget_set_accessible_name:
2229 * @widget: widget to set the accessible name for
2230 * @name: (allow-none): a character string to be set as the accessible name
2231 *
2232 * This method sets @name as the accessible name for @widget.
2233 *
2234 * Usually you will have no need to set the accessible name for an
2235 * object, as usually there is a label for most of the interface
2236 * elements. So in general it is better to just use
2237 * @st_widget_set_label_actor. This method is only required when you
2238 * need to set an accessible name and there is no available label
2239 * object.
2240 *
2241 */
2242 void
2243 st_widget_set_accessible_name (StWidget *widget,
2244 const gchar *name)
2245 {
2246 g_return_if_fail (ST_IS_WIDGET (widget));
2247
2248 if (widget->priv->accessible_name != NULL)
2249 g_free (widget->priv->accessible_name);
2250
2251 widget->priv->accessible_name = g_strdup (name);
2252 g_object_notify (G_OBJECT (widget), "accessible-name");
2253 }
2254
2255 /**
2256 * st_widget_get_accessible_name:
2257 * @widget: widget to get the accessible name for
2258 *
2259 * Gets the accessible name for this widget. See
2260 * st_widget_set_accessible_name() for more information.
2261 *
2262 * Return value: a character string representing the accessible name
2263 * of the widget.
2264 */
2265 const gchar *
2266 st_widget_get_accessible_name (StWidget *widget)
2267 {
2268 g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
2269
2270 return widget->priv->accessible_name;
2271 }
2272
2273 /**
2274 * st_widget_set_accessible_role:
2275 * @widget: widget to set the accessible role for
2276 * @role: The role to use
2277 *
2278 * This method sets @role as the accessible role for @widget. This
2279 * role describes what kind of user interface element @widget is and
2280 * is provided so that assistive technologies know how to present
2281 * @widget to the user.
2282 *
2283 * Usually you will have no need to set the accessible role for an
2284 * object, as this information is extracted from the context of the
2285 * object (ie: a #StButton has by default a push button role). This
2286 * method is only required when you need to redefine the role
2287 * currently associated with the widget, for instance if it is being
2288 * used in an unusual way (ie: a #StButton used as a togglebutton), or
2289 * if a generic object is used directly (ie: a container as a menu
2290 * item).
2291 *
2292 * If @role is #ATK_ROLE_INVALID, the role will not be changed
2293 * and the accessible's default role will be used instead.
2294 */
2295 void
2296 st_widget_set_accessible_role (StWidget *widget,
2297 AtkRole role)
2298 {
2299 g_return_if_fail (ST_IS_WIDGET (widget));
2300
2301 widget->priv->accessible_role = role;
2302
2303 g_object_notify (G_OBJECT (widget), "accessible-role");
2304 }
2305
2306
2307 /**
2308 * st_widget_get_accessible_role:
2309 * @widget: widget to get the accessible role for
2310 *
2311 * Gets the #AtkRole for this widget. See
2312 * st_widget_set_accessible_role() for more information.
2313 *
2314 * Return value: accessible #AtkRole for this widget
2315 */
2316 AtkRole
2317 st_widget_get_accessible_role (StWidget *widget)
2318 {
2319 AtkObject *accessible = NULL;
2320 AtkRole role = ATK_ROLE_INVALID;
2321
2322 g_return_val_if_fail (ST_IS_WIDGET (widget), ATK_ROLE_INVALID);
2323
2324 if (widget->priv->accessible_role != ATK_ROLE_INVALID)
2325 role = widget->priv->accessible_role;
2326 else if (widget->priv->accessible != NULL)
2327 role = atk_object_get_role (accessible);
2328
2329 return role;
2330 }
2331
2332 static void
2333 notify_accessible_state_change (StWidget *widget,
2334 AtkStateType state,
2335 gboolean value)
2336 {
2337 if (widget->priv->accessible != NULL)
2338 atk_object_notify_state_change (widget->priv->accessible, state, value);
2339 }
2340
2341 /**
2342 * st_widget_add_accessible_state:
2343 * @widget: A #StWidget
2344 * @state: #AtkStateType state to add
2345 *
2346 * This method adds @state as one of the accessible states for
2347 * @widget. The list of states of a widget describes the current state
2348 * of user interface element @widget and is provided so that assistive
2349 * technologies know how to present @widget to the user.
2350 *
2351 * Usually you will have no need to add accessible states for an
2352 * object, as the accessible object can extract most of the states
2353 * from the object itself (ie: a #StButton knows when it is pressed).
2354 * This method is only required when one cannot extract the
2355 * information automatically from the object itself (i.e.: a generic
2356 * container used as a toggle menu item will not automatically include
2357 * the toggled state).
2358 *
2359 */
2360 void
2361 st_widget_add_accessible_state (StWidget *widget,
2362 AtkStateType state)
2363 {
2364 g_return_if_fail (ST_IS_WIDGET (widget));
2365
2366 if (atk_state_set_add_state (widget->priv->local_state_set, state))
2367 notify_accessible_state_change (widget, state, TRUE);
2368 }
2369
2370 /**
2371 * st_widget_remove_accessible_state:
2372 * @widget: A #StWidget
2373 * @state: #AtkState state to remove
2374 *
2375 * This method removes @state as on of the accessible states for
2376 * @widget. See st_widget_add_accessible_state() for more information.
2377 *
2378 */
2379 void
2380 st_widget_remove_accessible_state (StWidget *widget,
2381 AtkStateType state)
2382 {
2383 g_return_if_fail (ST_IS_WIDGET (widget));
2384
2385 if (atk_state_set_remove_state (widget->priv->local_state_set, state))
2386 notify_accessible_state_change (widget, state, FALSE);
2387 }
2388
2389 /******************************************************************************/
2390 /*************************** ACCESSIBILITY SUPPORT ****************************/
2391 /******************************************************************************/
2392
2393 /* GObject */
2394
2395 static void st_widget_accessible_class_init (StWidgetAccessibleClass *klass);
2396 static void st_widget_accessible_init (StWidgetAccessible *widget);
2397 static void st_widget_accessible_dispose (GObject *gobject);
2398
2399 /* AtkObject */
2400 static AtkStateSet *st_widget_accessible_ref_state_set (AtkObject *obj);
2401 static void st_widget_accessible_initialize (AtkObject *obj,
2402 gpointer data);
2403 static AtkRole st_widget_accessible_get_role (AtkObject *obj);
2404
2405 /* Private methods */
2406 static void on_pseudo_class_notify (GObject *gobject,
2407 GParamSpec *pspec,
2408 gpointer data);
2409 static void on_can_focus_notify (GObject *gobject,
2410 GParamSpec *pspec,
2411 gpointer data);
2412 static void on_label_notify (GObject *gobject,
2413 GParamSpec *pspec,
2414 gpointer data);
2415 static void check_pseudo_class (StWidgetAccessible *self,
2416 StWidget *widget);
2417 static void check_labels (StWidgetAccessible *self,
2418 StWidget *widget);
2419
2420 G_DEFINE_TYPE (StWidgetAccessible, st_widget_accessible, CALLY_TYPE_ACTOR)
2421
2422 #define ST_WIDGET_ACCESSIBLE_GET_PRIVATE(obj) \
2423 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_WIDGET_ACCESSIBLE, \
2424 StWidgetAccessiblePrivate))
2425
2426 struct _StWidgetAccessiblePrivate
2427 {
2428 /* Cached values (used to avoid extra notifications) */
2429 gboolean selected;
2430 gboolean checked;
2431
2432 /* The current_label. Right now there are the proper atk
2433 * relationships between this object and the label
2434 */
2435 AtkObject *current_label;
2436 };
2437
2438
2439 static AtkObject *
2440 st_widget_get_accessible (ClutterActor *actor)
2441 {
2442 StWidget *widget = NULL;
2443
2444 g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
2445
2446 widget = ST_WIDGET (actor);
2447
2448 if (widget->priv->accessible == NULL)
2449 {
2450 widget->priv->accessible =
2451 g_object_new (ST_WIDGET_GET_CLASS (widget)->get_accessible_type (),
2452 NULL);
2453
2454 atk_object_initialize (widget->priv->accessible, actor);
2455 }
2456
2457 return widget->priv->accessible;
2458 }
2459
2460 static const gchar *
2461 st_widget_accessible_get_name (AtkObject *obj)
2462 {
2463 const gchar* name = NULL;
2464
2465 g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), NULL);
2466
2467 name = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_name (obj);
2468 if (name == NULL)
2469 {
2470 StWidget *widget = NULL;
2471
2472 widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
2473
2474 if (widget == NULL)
2475 name = NULL;
2476 else
2477 name = widget->priv->accessible_name;
2478 }
2479
2480 return name;
2481 }
2482
2483 static void
2484 st_widget_accessible_class_init (StWidgetAccessibleClass *klass)
2485 {
2486 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
2487 AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
2488
2489 gobject_class->dispose = st_widget_accessible_dispose;
2490
2491 atk_class->ref_state_set = st_widget_accessible_ref_state_set;
2492 atk_class->initialize = st_widget_accessible_initialize;
2493 atk_class->get_role = st_widget_accessible_get_role;
2494 atk_class->get_name = st_widget_accessible_get_name;
2495
2496 g_type_class_add_private (gobject_class, sizeof (StWidgetAccessiblePrivate));
2497 }
2498
2499 static void
2500 st_widget_accessible_init (StWidgetAccessible *self)
2501 {
2502 StWidgetAccessiblePrivate *priv = ST_WIDGET_ACCESSIBLE_GET_PRIVATE (self);
2503
2504 self->priv = priv;
2505 }
2506
2507 static void
2508 st_widget_accessible_dispose (GObject *gobject)
2509 {
2510 StWidgetAccessible *self = ST_WIDGET_ACCESSIBLE (gobject);
2511
2512 if (self->priv->current_label)
2513 {
2514 g_object_unref (self->priv->current_label);
2515 self->priv->current_label = NULL;
2516 }
2517
2518 G_OBJECT_CLASS (st_widget_accessible_parent_class)->dispose (gobject);
2519 }
2520
2521 static void
2522 on_accessible_name_notify (GObject *gobject,
2523 GParamSpec *pspec,
2524 AtkObject *accessible)
2525 {
2526 g_object_notify (G_OBJECT (accessible), "accessible-name");
2527 }
2528
2529 static void
2530 st_widget_accessible_initialize (AtkObject *obj,
2531 gpointer data)
2532 {
2533 ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->initialize (obj, data);
2534
2535 g_signal_connect (data, "notify::pseudo-class",
2536 G_CALLBACK (on_pseudo_class_notify),
2537 obj);
2538
2539 g_signal_connect (data, "notify::can-focus",
2540 G_CALLBACK (on_can_focus_notify),
2541 obj);
2542
2543 g_signal_connect (data, "notify::label-actor",
2544 G_CALLBACK (on_label_notify),
2545 obj);
2546
2547 g_signal_connect (data, "notify::accessible-name",
2548 G_CALLBACK (on_accessible_name_notify),
2549 obj);
2550
2551 /* Check the cached selected state and notify the first selection.
2552 * Ie: it is required to ensure a first notification when Alt+Tab
2553 * popup appears
2554 */
2555 check_pseudo_class (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
2556 check_labels (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
2557 }
2558
2559 static AtkStateSet *
2560 st_widget_accessible_ref_state_set (AtkObject *obj)
2561 {
2562 AtkStateSet *result = NULL;
2563 AtkStateSet *aux_set = NULL;
2564 ClutterActor *actor = NULL;
2565 StWidget *widget = NULL;
2566 StWidgetAccessible *self = NULL;
2567
2568 result = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->ref_state_set (obj);
2569
2570 actor = CLUTTER_ACTOR (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
2571
2572 if (actor == NULL) /* State is defunct */
2573 return result;
2574
2575 widget = ST_WIDGET (actor);
2576 self = ST_WIDGET_ACCESSIBLE (obj);
2577
2578 /* priv->selected should be properly updated on the
2579 * ATK_STATE_SELECTED notification callbacks
2580 */
2581 if (self->priv->selected)
2582 atk_state_set_add_state (result, ATK_STATE_SELECTED);
2583
2584 if (self->priv->checked)
2585 atk_state_set_add_state (result, ATK_STATE_CHECKED);
2586
2587 /* On clutter there isn't any tip to know if a actor is focusable or
2588 * not, anyone can receive the key_focus. For this reason
2589 * cally_actor sets any actor as FOCUSABLE. This is not the case on
2590 * St, where we have can_focus. But this means that we need to
2591 * remove the state FOCUSABLE if it is not focusable
2592 */
2593 if (st_widget_get_can_focus (widget))
2594 atk_state_set_add_state (result, ATK_STATE_FOCUSABLE);
2595 else
2596 atk_state_set_remove_state (result, ATK_STATE_FOCUSABLE);
2597
2598 /* We add the states added externally if required */
2599 if (!atk_state_set_is_empty (widget->priv->local_state_set))
2600 {
2601 aux_set = atk_state_set_or_sets (result, widget->priv->local_state_set);
2602
2603 g_object_unref (result); /* previous result will not be used */
2604 result = aux_set;
2605 }
2606
2607 return result;
2608 }
2609
2610 static AtkRole
2611 st_widget_accessible_get_role (AtkObject *obj)
2612 {
2613 StWidget *widget = NULL;
2614
2615 g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), ATK_ROLE_INVALID);
2616
2617 widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
2618
2619 if (widget == NULL)
2620 return ATK_ROLE_INVALID;
2621
2622 if (widget->priv->accessible_role != ATK_ROLE_INVALID)
2623 return widget->priv->accessible_role;
2624
2625 return ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_role (obj);
2626 }
2627
2628 static void
2629 on_pseudo_class_notify (GObject *gobject,
2630 GParamSpec *pspec,
2631 gpointer data)
2632 {
2633 check_pseudo_class (ST_WIDGET_ACCESSIBLE (data),
2634 ST_WIDGET (gobject));
2635 }
2636
2637 /*
2638 * In some cases the only way to check some states are checking the
2639 * pseudo-class. Like if the object is selected (see bug 637830) or if
2640 * the object is toggled. This method also notifies a state change if
2641 * the value is different to the one cached.
2642 *
2643 * We also assume that if the object uses that pseudo-class, it makes
2644 * sense to notify that state change. It would be possible to refine
2645 * that behaviour checking the role (ie: notify CHECKED changes only
2646 * for CHECK_BUTTON roles).
2647 *
2648 * In a ideal world we would have a more standard way to get the
2649 * state, like the widget-context (as in the case of
2650 * gtktreeview-cells), or something like the property "can-focus". But
2651 * for the moment this is enough, and we can update that in the future
2652 * if required.
2653 */
2654 static void
2655 check_pseudo_class (StWidgetAccessible *self,
2656 StWidget *widget)
2657 {
2658 gboolean found = FALSE;
2659
2660 found = st_widget_has_style_pseudo_class (widget,
2661 "selected");
2662
2663 if (found != self->priv->selected)
2664 {
2665 self->priv->selected = found;
2666 atk_object_notify_state_change (ATK_OBJECT (self),
2667 ATK_STATE_SELECTED,
2668 found);
2669 }
2670
2671 found = st_widget_has_style_pseudo_class (widget,
2672 "checked");
2673 if (found != self->priv->checked)
2674 {
2675 self->priv->checked = found;
2676 atk_object_notify_state_change (ATK_OBJECT (self),
2677 ATK_STATE_CHECKED,
2678 found);
2679 }
2680 }
2681
2682 static void
2683 on_can_focus_notify (GObject *gobject,
2684 GParamSpec *pspec,
2685 gpointer data)
2686 {
2687 gboolean can_focus = st_widget_get_can_focus (ST_WIDGET (gobject));
2688
2689 atk_object_notify_state_change (ATK_OBJECT (data),
2690 ATK_STATE_FOCUSABLE, can_focus);
2691 }
2692
2693 static void
2694 on_label_notify (GObject *gobject,
2695 GParamSpec *pspec,
2696 gpointer data)
2697 {
2698 check_labels (ST_WIDGET_ACCESSIBLE (data), ST_WIDGET (gobject));
2699 }
2700
2701 static void
2702 check_labels (StWidgetAccessible *widget_accessible,
2703 StWidget *widget)
2704 {
2705 ClutterActor *label = NULL;
2706 AtkObject *label_accessible = NULL;
2707
2708 /* We only call this method at startup, and when the label changes,
2709 * so it is fine to remove the previous relationships if we have the
2710 * current_label by default
2711 */
2712 if (widget_accessible->priv->current_label != NULL)
2713 {
2714 AtkObject *previous_label = widget_accessible->priv->current_label;
2715
2716 atk_object_remove_relationship (ATK_OBJECT (widget_accessible),
2717 ATK_RELATION_LABELLED_BY,
2718 previous_label);
2719
2720 atk_object_remove_relationship (previous_label,
2721 ATK_RELATION_LABEL_FOR,
2722 ATK_OBJECT (widget_accessible));
2723
2724 g_object_unref (previous_label);
2725 }
2726
2727 label = st_widget_get_label_actor (widget);
2728 if (label == NULL)
2729 {
2730 widget_accessible->priv->current_label = NULL;
2731 }
2732 else
2733 {
2734 label_accessible = clutter_actor_get_accessible (label);
2735 widget_accessible->priv->current_label = g_object_ref (label_accessible);
2736
2737 atk_object_add_relationship (ATK_OBJECT (widget_accessible),
2738 ATK_RELATION_LABELLED_BY,
2739 label_accessible);
2740
2741 atk_object_add_relationship (label_accessible,
2742 ATK_RELATION_LABEL_FOR,
2743 ATK_OBJECT (widget_accessible));
2744 }
2745 }
2746
2747 /**
2748 * st_widget_get_focus_chain:
2749 * @widget: An #StWidget
2750 *
2751 * Gets a list of the focusable children of @widget, in "Tab"
2752 * order. By default, this returns all visible
2753 * (as in CLUTTER_ACTOR_IS_VISIBLE()) children of @widget.
2754 *
2755 * Returns: (element-type Clutter.Actor) (transfer container):
2756 * @widget's focusable children
2757 */
2758 GList *
2759 st_widget_get_focus_chain (StWidget *widget)
2760 {
2761 return ST_WIDGET_GET_CLASS (widget)->get_focus_chain (widget);
2762 }
2763
2764 /**
2765 * st_widget_clear_background_image:
2766 * @widget: An #StWidget
2767 *
2768 * Force a reload of the background-image property. Usually properties
2769 * are cached heavily to avoid unnecessary work on paint, this method
2770 * will force the cache to be recreated.
2771 */
2772 void
2773 st_widget_clear_background_image (StWidget *actor)
2774 {
2775 GFile *file;
2776 const char *path;
2777 char *uri;
2778
2779 if (actor->priv->theme_node == NULL)
2780 return;
2781
2782 path = st_theme_node_get_background_image (actor->priv->theme_node);
2783 if (path == NULL)
2784 return;
2785
2786 file = g_file_new_for_path (path);
2787 uri = g_file_get_uri (file);
2788
2789 st_texture_cache_clear_uri (st_texture_cache_get_default (), uri);
2790 st_theme_node_invalidate_paint_state (actor->priv->theme_node);
2791
2792 if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR (actor)))
2793 clutter_actor_queue_redraw (CLUTTER_ACTOR (actor));
2794
2795 g_object_unref (file);
2796 g_free (uri);
2797 }