1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3  * st-entry.c: Plain entry actor
4  *
5  * Copyright 2008, 2009 Intel Corporation
6  * Copyright 2009, 2010 Red Hat, Inc.
7  * Copyright 2010 Florian Müllner
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms and conditions of the GNU Lesser General Public License,
11  * version 2.1, as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope it will be useful, but WITHOUT ANY
14  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
16  * more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 /**
23  * SECTION:st-entry
24  * @short_description: Widget for displaying text
25  *
26  * #StEntry is a simple widget for displaying text. It derives from
27  * #StWidget to add extra style and placement functionality over
28  * #ClutterText. The internal #ClutterText is publicly accessibly to allow
29  * applications to set further properties.
30  *
31  * #StEntry supports the following pseudo style states:
32  * <itemizedlist>
33  *  <listitem>
34  *   <para>focus: the widget has focus</para>
35  *  </listitem>
36  *  <listitem>
37  *   <para>indeterminate: the widget is showing the hint text</para>
38  *  </listitem>
39  *  <listitem>
40  *   <para>hover: the widget is showing the hint text and is underneath the
41  *                pointer</para>
42  *  </listitem>
43  * </itemizedlist>
44  */
45 
46 #ifdef HAVE_CONFIG_H
47 #include "config.h"
48 #endif
49 
50 #include <math.h>
51 
52 #include <stdlib.h>
53 #include <string.h>
54 
55 #include <glib.h>
56 #include <gtk/gtk.h>
57 
58 #include <clutter/clutter.h>
59 
60 #include "st-entry.h"
61 
62 #include "st-im-text.h"
63 #include "st-icon.h"
64 #include "st-widget.h"
65 #include "st-texture-cache.h"
66 #include "st-clipboard.h"
67 #include "st-private.h"
68 
69 #include "st-widget-accessible.h"
70 
71 #define HAS_FOCUS(actor) (clutter_actor_get_stage (actor) && clutter_stage_get_key_focus ((ClutterStage *) clutter_actor_get_stage (actor)) == actor)
72 
73 
74 /* properties */
75 enum
76 {
77   PROP_0,
78 
79   PROP_CLUTTER_TEXT,
80   PROP_HINT_TEXT,
81   PROP_TEXT,
82 };
83 
84 /* signals */
85 enum
86 {
87   PRIMARY_ICON_CLICKED,
88   SECONDARY_ICON_CLICKED,
89 
90   LAST_SIGNAL
91 };
92 
93 #define ST_ENTRY_PRIV(x) ((StEntry *) x)->priv
94 
95 static void         st_entry_check_cursor_blink       (StEntry       *entry);
96 static void         st_entry_pend_cursor_blink        (StEntry       *entry);
97 static void         st_entry_reset_blink_time         (StEntry       *entry);
98 
99 struct _StEntryPrivate
100 {
101   ClutterActor *entry;
102   gchar        *hint;
103 
104   ClutterActor *primary_icon;
105   ClutterActor *secondary_icon;
106 
107   gfloat        spacing;
108 
109   gboolean      hint_visible;
110   gboolean      capslock_warning_shown;
111   guint         blink_time;
112   guint         blink_timeout;
113   gboolean      cursor_visible;
114 };
115 
116 static guint entry_signals[LAST_SIGNAL] = { 0, };
117 
118 G_DEFINE_TYPE_WITH_PRIVATE (StEntry, st_entry, ST_TYPE_WIDGET);
119 
120 static GType st_entry_accessible_get_type (void) G_GNUC_CONST;
121 
122 static void
st_entry_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)123 st_entry_set_property (GObject      *gobject,
124                        guint         prop_id,
125                        const GValue *value,
126                        GParamSpec   *pspec)
127 {
128   StEntry *entry = ST_ENTRY (gobject);
129 
130   switch (prop_id)
131     {
132     case PROP_HINT_TEXT:
133       st_entry_set_hint_text (entry, g_value_get_string (value));
134       break;
135 
136     case PROP_TEXT:
137       st_entry_set_text (entry, g_value_get_string (value));
138       break;
139 
140     default:
141       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
142       break;
143     }
144 }
145 
146 static void
st_entry_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)147 st_entry_get_property (GObject    *gobject,
148                        guint       prop_id,
149                        GValue     *value,
150                        GParamSpec *pspec)
151 {
152   StEntryPrivate *priv = ST_ENTRY_PRIV (gobject);
153 
154   switch (prop_id)
155     {
156     case PROP_CLUTTER_TEXT:
157       g_value_set_object (value, priv->entry);
158       break;
159 
160     case PROP_HINT_TEXT:
161       g_value_set_string (value, priv->hint);
162       break;
163 
164     case PROP_TEXT:
165       g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->entry)));
166       break;
167 
168     default:
169       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
170       break;
171     }
172 }
173 
174 static void
show_capslock_feedback(StEntry * entry)175 show_capslock_feedback (StEntry *entry)
176 {
177   if (entry->priv->secondary_icon == NULL)
178     {
179       ClutterActor *icon = g_object_new (ST_TYPE_ICON,
180                                          "style-class", "capslock-warning",
181                                          "icon-type", ST_ICON_SYMBOLIC,
182                                          "icon-name", "dialog-warning",
183                                          NULL);
184 
185       st_entry_set_secondary_icon (entry, icon);
186       entry->priv->capslock_warning_shown = TRUE;
187     }
188 }
189 
190 static void
remove_capslock_feedback(StEntry * entry)191 remove_capslock_feedback (StEntry *entry)
192 {
193   if (entry->priv->capslock_warning_shown)
194     {
195       st_entry_set_secondary_icon (entry, NULL);
196       entry->priv->capslock_warning_shown = FALSE;
197     }
198 }
199 
200 static void
keymap_state_changed(GdkKeymap * keymap,gpointer user_data)201 keymap_state_changed (GdkKeymap *keymap,
202                       gpointer   user_data)
203 {
204   StEntry *entry = ST_ENTRY (user_data);
205 
206   if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) != 0)
207     {
208       if (gdk_keymap_get_caps_lock_state (keymap))
209         show_capslock_feedback (entry);
210       else
211         remove_capslock_feedback (entry);
212     }
213 }
214 
215 static void
st_entry_dispose(GObject * object)216 st_entry_dispose (GObject *object)
217 {
218   StEntry *entry = ST_ENTRY (object);
219   StEntryPrivate *priv = entry->priv;
220   GdkKeymap *keymap;
221 
222   if (priv->blink_timeout)
223     {
224       g_source_remove (priv->blink_timeout);
225       priv->blink_timeout = 0;
226     }
227 
228   keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
229   g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
230 
231   G_OBJECT_CLASS (st_entry_parent_class)->dispose (object);
232 }
233 
234 static void
st_entry_finalize(GObject * object)235 st_entry_finalize (GObject *object)
236 {
237   StEntryPrivate *priv = ST_ENTRY_PRIV (object);
238 
239   g_free (priv->hint);
240   priv->hint = NULL;
241 
242   G_OBJECT_CLASS (st_entry_parent_class)->finalize (object);
243 }
244 
245 static void
st_entry_style_changed(StWidget * self)246 st_entry_style_changed (StWidget *self)
247 {
248   StEntryPrivate *priv = ST_ENTRY_PRIV (self);
249   StThemeNode *theme_node;
250   ClutterColor color;
251   gdouble size;
252 
253   theme_node = st_widget_get_theme_node (self);
254 
255   if (st_theme_node_lookup_length (theme_node, "caret-size", TRUE, &size))
256     clutter_text_set_cursor_size (CLUTTER_TEXT (priv->entry), (int)(.5 + size));
257 
258   if (st_theme_node_lookup_color (theme_node, "caret-color", TRUE, &color))
259     clutter_text_set_cursor_color (CLUTTER_TEXT (priv->entry), &color);
260 
261   if (st_theme_node_lookup_color (theme_node, "selection-background-color", TRUE, &color))
262     clutter_text_set_selection_color (CLUTTER_TEXT (priv->entry), &color);
263 
264   if (st_theme_node_lookup_color (theme_node, "selected-color", TRUE, &color))
265     clutter_text_set_selected_text_color (CLUTTER_TEXT (priv->entry), &color);
266 
267   _st_set_text_from_style ((ClutterText *)priv->entry, theme_node);
268 
269   ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
270 }
271 
272 static gboolean
st_entry_navigate_focus(StWidget * widget,ClutterActor * from,GtkDirectionType direction)273 st_entry_navigate_focus (StWidget         *widget,
274                          ClutterActor     *from,
275                          GtkDirectionType  direction)
276 {
277   StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
278 
279   /* This is basically the same as st_widget_real_navigate_focus(),
280    * except that widget is behaving as a proxy for priv->entry (which
281    * isn't an StWidget and so has no can-focus flag of its own).
282    */
283 
284   if (from == priv->entry)
285     return FALSE;
286   else if (st_widget_get_can_focus (widget))
287     {
288       clutter_actor_grab_key_focus (priv->entry);
289       return TRUE;
290     }
291   else
292     return FALSE;
293 }
294 
295 static void
st_entry_get_preferred_width(ClutterActor * actor,gfloat for_height,gfloat * min_width_p,gfloat * natural_width_p)296 st_entry_get_preferred_width (ClutterActor *actor,
297                               gfloat        for_height,
298                               gfloat       *min_width_p,
299                               gfloat       *natural_width_p)
300 {
301   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
302   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
303   gfloat icon_w;
304 
305   st_theme_node_adjust_for_height (theme_node, &for_height);
306 
307   clutter_actor_get_preferred_width (priv->entry, for_height,
308                                      min_width_p,
309                                      natural_width_p);
310 
311   if (priv->primary_icon)
312     {
313       clutter_actor_get_preferred_width (priv->primary_icon, -1, NULL, &icon_w);
314 
315       if (min_width_p)
316         *min_width_p += icon_w + priv->spacing;
317 
318       if (natural_width_p)
319         *natural_width_p += icon_w + priv->spacing;
320     }
321 
322   if (priv->secondary_icon)
323     {
324       clutter_actor_get_preferred_width (priv->secondary_icon,
325                                          -1, NULL, &icon_w);
326 
327       if (min_width_p)
328         *min_width_p += icon_w + priv->spacing;
329 
330       if (natural_width_p)
331         *natural_width_p += icon_w + priv->spacing;
332     }
333 
334   st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
335 }
336 
337 static void
st_entry_get_preferred_height(ClutterActor * actor,gfloat for_width,gfloat * min_height_p,gfloat * natural_height_p)338 st_entry_get_preferred_height (ClutterActor *actor,
339                                gfloat        for_width,
340                                gfloat       *min_height_p,
341                                gfloat       *natural_height_p)
342 {
343   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
344   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
345   gfloat icon_h;
346 
347   st_theme_node_adjust_for_width (theme_node, &for_width);
348 
349   clutter_actor_get_preferred_height (priv->entry, for_width,
350                                       min_height_p,
351                                       natural_height_p);
352 
353   if (priv->primary_icon)
354     {
355       clutter_actor_get_preferred_height (priv->primary_icon,
356                                           -1, NULL, &icon_h);
357 
358       if (min_height_p && icon_h > *min_height_p)
359         *min_height_p = icon_h;
360 
361       if (natural_height_p && icon_h > *natural_height_p)
362         *natural_height_p = icon_h;
363     }
364 
365   if (priv->secondary_icon)
366     {
367       clutter_actor_get_preferred_height (priv->secondary_icon,
368                                           -1, NULL, &icon_h);
369 
370       if (min_height_p && icon_h > *min_height_p)
371         *min_height_p = icon_h;
372 
373       if (natural_height_p && icon_h > *natural_height_p)
374         *natural_height_p = icon_h;
375     }
376 
377   st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
378 }
379 
380 static void
st_entry_allocate(ClutterActor * actor,const ClutterActorBox * box,ClutterAllocationFlags flags)381 st_entry_allocate (ClutterActor          *actor,
382                    const ClutterActorBox *box,
383                    ClutterAllocationFlags flags)
384 {
385   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
386   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
387   ClutterActorBox content_box, child_box, icon_box;
388   gfloat icon_w, icon_h;
389   gfloat entry_h, min_h, pref_h, avail_h;
390 
391   clutter_actor_set_allocation (actor, box, flags);
392 
393   st_theme_node_get_content_box (theme_node, box, &content_box);
394 
395   avail_h = content_box.y2 - content_box.y1;
396 
397   child_box.x1 = content_box.x1;
398   child_box.x2 = content_box.x2;
399 
400   if (priv->primary_icon)
401     {
402       clutter_actor_get_preferred_width (priv->primary_icon,
403                                          -1, NULL, &icon_w);
404       clutter_actor_get_preferred_height (priv->primary_icon,
405                                           -1, NULL, &icon_h);
406 
407       icon_box.x1 = content_box.x1;
408       icon_box.x2 = icon_box.x1 + icon_w;
409 
410       icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
411       icon_box.y2 = icon_box.y1 + icon_h;
412 
413       clutter_actor_allocate (priv->primary_icon,
414                               &icon_box,
415                               flags);
416 
417       /* reduce the size for the entry */
418       child_box.x1 = MIN (child_box.x2, child_box.x1 + icon_w + priv->spacing);
419     }
420 
421   if (priv->secondary_icon)
422     {
423       clutter_actor_get_preferred_width (priv->secondary_icon,
424                                          -1, NULL, &icon_w);
425       clutter_actor_get_preferred_height (priv->secondary_icon,
426                                           -1, NULL, &icon_h);
427 
428       icon_box.x2 = content_box.x2;
429       icon_box.x1 = icon_box.x2 - icon_w;
430 
431       icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
432       icon_box.y2 = icon_box.y1 + icon_h;
433 
434       clutter_actor_allocate (priv->secondary_icon,
435                               &icon_box,
436                               flags);
437 
438       /* reduce the size for the entry */
439        child_box.x2 = MAX (child_box.x1, child_box.x2 - icon_w - priv->spacing);
440     }
441 
442   clutter_actor_get_preferred_height (priv->entry, child_box.x2 - child_box.x1,
443                                       &min_h, &pref_h);
444 
445   entry_h = CLAMP (pref_h, min_h, avail_h);
446 
447   child_box.y1 = (int) (content_box.y1 + avail_h / 2 - entry_h / 2);
448   child_box.y2 = child_box.y1 + entry_h;
449 
450   clutter_actor_allocate (priv->entry, &child_box, flags);
451 }
452 
453 #define CURSOR_ON_MULTIPLIER 2
454 #define CURSOR_OFF_MULTIPLIER 1
455 #define CURSOR_PEND_MULTIPLIER 3
456 #define CURSOR_DIVIDER 3
457 
458 static gboolean
cursor_blinks(StEntry * entry)459 cursor_blinks (StEntry *entry)
460 {
461   StEntryPrivate *priv = entry->priv;
462 
463   if (clutter_actor_has_key_focus (CLUTTER_ACTOR (priv->entry)) &&
464       clutter_text_get_editable (CLUTTER_TEXT (priv->entry)) &&
465       clutter_text_get_selection_bound (CLUTTER_TEXT (priv->entry)) == clutter_text_get_cursor_position (CLUTTER_TEXT (priv->entry)))
466     {
467       gboolean blink;
468       g_object_get (gtk_settings_get_default (), "gtk-cursor-blink", &blink, NULL);
469 
470       return blink;
471     }
472   else
473     return FALSE;
474 }
475 
476 static gint
get_cursor_time(StEntry * entry)477 get_cursor_time (StEntry *entry)
478 {
479   gint time;
480   g_object_get (gtk_settings_get_default (), "gtk-cursor-blink-time", &time, NULL);
481 
482   return time;
483 }
484 
485 static gint
get_cursor_blink_timeout(StEntry * entry)486 get_cursor_blink_timeout (StEntry *entry)
487 {
488   gint timeout;
489   g_object_get (gtk_settings_get_default (), "gtk-cursor-blink-timeout", &timeout, NULL);
490 
491   return timeout;
492 }
493 
494 static void
show_cursor(StEntry * entry)495 show_cursor (StEntry *entry)
496 {
497   StEntryPrivate *priv = entry->priv;
498 
499   if (!priv->cursor_visible)
500     {
501       priv->cursor_visible = TRUE;
502       clutter_text_set_cursor_visible (CLUTTER_TEXT (priv->entry), TRUE);
503     }
504 }
505 
506 static void
hide_cursor(StEntry * entry)507 hide_cursor (StEntry *entry)
508 {
509   StEntryPrivate *priv = entry->priv;
510 
511   if (priv->cursor_visible)
512     {
513       priv->cursor_visible = FALSE;
514       clutter_text_set_cursor_visible (CLUTTER_TEXT (priv->entry), FALSE);
515     }
516 }
517 
518 /*
519  * Blink!
520  */
521 static gint
blink_cb(gpointer data)522 blink_cb (gpointer data)
523 {
524   StEntry *entry;
525   StEntryPrivate *priv;
526   guint blink_timeout;
527 
528   entry = ST_ENTRY (data);
529   priv = entry->priv;
530 
531   if (!clutter_actor_has_key_focus (priv->entry))
532     {
533       g_warning ("StEntry - did not receive key-focus-out event. If you\n"
534          "connect a handler to this signal, it must return\n"
535          "FALSE so the StEntry gets the event as well");
536 
537       st_entry_check_cursor_blink (entry);
538 
539       return FALSE;
540     }
541 
542   if (clutter_text_get_selection_bound (CLUTTER_TEXT (priv->entry)) != clutter_text_get_cursor_position (CLUTTER_TEXT (priv->entry)))
543     {
544       st_entry_check_cursor_blink (entry);
545       return FALSE;
546     }
547 
548   blink_timeout = get_cursor_blink_timeout (entry);
549   if (priv->blink_time > 1000 * blink_timeout &&
550       blink_timeout < G_MAXINT/1000)
551     {
552       /* we've blinked enough without the user doing anything, stop blinking */
553       show_cursor (entry);
554       priv->blink_timeout = 0;
555     }
556   else if (priv->cursor_visible)
557     {
558       hide_cursor (entry);
559       priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_OFF_MULTIPLIER / CURSOR_DIVIDER,
560                         blink_cb,
561                         entry);
562     }
563   else
564     {
565       show_cursor (entry);
566       priv->blink_time += get_cursor_time (entry);
567       priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER,
568                         blink_cb,
569                         entry);
570     }
571 
572   /* Remove ourselves */
573   return FALSE;
574 }
575 
576 static void
st_entry_check_cursor_blink(StEntry * entry)577 st_entry_check_cursor_blink (StEntry *entry)
578 {
579   StEntryPrivate *priv = entry->priv;
580 
581   if (cursor_blinks (entry))
582     {
583       if (!priv->blink_timeout)
584     {
585       show_cursor (entry);
586       priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER,
587                         blink_cb,
588                         entry);
589     }
590     }
591   else
592     {
593       if (priv->blink_timeout)
594         {
595           g_source_remove (priv->blink_timeout);
596           priv->blink_timeout = 0;
597         }
598 
599       show_cursor (entry);
600     }
601 }
602 
603 static void
st_entry_pend_cursor_blink(StEntry * entry)604 st_entry_pend_cursor_blink (StEntry *entry)
605 {
606   StEntryPrivate *priv = entry->priv;
607 
608   if (cursor_blinks (entry))
609     {
610       if (priv->blink_timeout != 0)
611     g_source_remove (priv->blink_timeout);
612 
613       priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_PEND_MULTIPLIER / CURSOR_DIVIDER,
614                                                      blink_cb,
615                                                      entry);
616       show_cursor (entry);
617     }
618 }
619 
620 static void
st_entry_reset_blink_time(StEntry * entry)621 st_entry_reset_blink_time (StEntry *entry)
622 {
623   StEntryPrivate *priv = entry->priv;
624 
625   priv->blink_time = 0;
626 }
627 
628 static void
clutter_text_focus_in_cb(ClutterText * text,ClutterActor * actor)629 clutter_text_focus_in_cb (ClutterText  *text,
630                           ClutterActor *actor)
631 {
632   StEntry *entry = ST_ENTRY (actor);
633   StEntryPrivate *priv = entry->priv;
634   GdkKeymap *keymap;
635 
636   /* remove the hint if visible */
637   if (priv->hint && priv->hint_visible)
638     {
639       priv->hint_visible = FALSE;
640 
641       clutter_text_set_text (text, "");
642     }
643 
644   keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
645   keymap_state_changed (keymap, entry);
646   g_signal_connect (keymap, "state-changed",
647                     G_CALLBACK (keymap_state_changed), entry);
648 
649   st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
650   st_widget_add_style_pseudo_class (ST_WIDGET (actor), "focus");
651 
652   st_entry_reset_blink_time (entry);
653   st_entry_check_cursor_blink (entry);
654 }
655 
656 static void
clutter_text_focus_out_cb(ClutterText * text,ClutterActor * actor)657 clutter_text_focus_out_cb (ClutterText  *text,
658                            ClutterActor *actor)
659 {
660   StEntry *entry = ST_ENTRY (actor);
661   StEntryPrivate *priv = entry->priv;
662   GdkKeymap *keymap;
663 
664   st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "focus");
665 
666   /* add a hint if the entry is empty */
667   if (priv->hint && !strcmp (clutter_text_get_text (text), ""))
668     {
669       priv->hint_visible = TRUE;
670 
671       clutter_text_set_text (text, priv->hint);
672       st_widget_add_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
673     }
674   st_entry_check_cursor_blink (entry);
675   remove_capslock_feedback (entry);
676 
677   keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
678   g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
679 }
680 
681 static void
clutter_text_password_char_cb(GObject * object,GParamSpec * pspec,gpointer user_data)682 clutter_text_password_char_cb (GObject    *object,
683                                GParamSpec *pspec,
684                                gpointer    user_data)
685 {
686   StEntry *entry = ST_ENTRY (user_data);
687 
688   if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) == 0)
689     remove_capslock_feedback (entry);
690 }
691 
692 static void
clutter_text_selection_bound_cb(GObject * object,GParamSpec * pspec,gpointer user_data)693 clutter_text_selection_bound_cb (GObject    *object,
694                                  GParamSpec *pspec,
695                                  gpointer    user_data)
696 {
697   StEntry *entry = ST_ENTRY (user_data);
698 
699   st_entry_reset_blink_time (entry);
700   st_entry_pend_cursor_blink (entry);
701 }
702 
703 static void
clutter_text_cursor_changed(ClutterText * text,ClutterActor * actor)704 clutter_text_cursor_changed (ClutterText *text, ClutterActor *actor)
705 {
706   st_entry_reset_blink_time (ST_ENTRY (actor));
707   st_entry_pend_cursor_blink (ST_ENTRY (actor));
708 }
709 
710 static void
st_entry_clipboard_callback(StClipboard * clipboard,const gchar * text,gpointer data)711 st_entry_clipboard_callback (StClipboard *clipboard,
712                              const gchar *text,
713                              gpointer     data)
714 {
715   ClutterText *ctext;
716   gint cursor_pos;
717 
718   if (!text)
719     return;
720 
721   ctext = (ClutterText*)((StEntry *) data)->priv->entry;
722 
723   /* delete the current selection before pasting */
724   clutter_text_delete_selection (ctext);
725 
726   /* "paste" the clipboard text into the entry */
727   cursor_pos = clutter_text_get_cursor_position (ctext);
728   clutter_text_insert_text (ctext, text, cursor_pos);
729 }
730 
731 static gboolean
clutter_text_button_press_event(ClutterActor * actor,ClutterButtonEvent * event,gpointer user_data)732 clutter_text_button_press_event (ClutterActor       *actor,
733                                  ClutterButtonEvent *event,
734                                  gpointer            user_data)
735 {
736   StEntryPrivate *priv = ST_ENTRY_PRIV (user_data);
737   GtkSettings *settings = gtk_settings_get_default ();
738   gboolean primary_paste_enabled;
739 
740   g_object_get (settings,
741                 "gtk-enable-primary-paste", &primary_paste_enabled,
742                 NULL);
743 
744   if (primary_paste_enabled && event->button == 2
745       && clutter_text_get_editable (CLUTTER_TEXT (priv->entry)))
746     {
747       StClipboard *clipboard;
748 
749       clipboard = st_clipboard_get_default ();
750 
751       /* By the time the clipboard callback is called,
752        * the rest of the signal handlers will have
753        * run, making the text cursor to be in the correct
754        * place.
755        */
756       st_clipboard_get_text (clipboard,
757                              ST_CLIPBOARD_TYPE_PRIMARY,
758                              st_entry_clipboard_callback,
759                              user_data);
760     }
761 
762   return FALSE;
763 }
764 
765 
766 static gboolean
st_entry_key_press_event(ClutterActor * actor,ClutterKeyEvent * event)767 st_entry_key_press_event (ClutterActor    *actor,
768                           ClutterKeyEvent *event)
769 {
770   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
771 
772   st_entry_reset_blink_time (ST_ENTRY (actor));
773   st_entry_pend_cursor_blink (ST_ENTRY (actor));
774 
775   /* This is expected to handle events that were emitted for the inner
776      ClutterText. They only reach this function if the ClutterText
777      didn't handle them */
778 
779   /* paste */
780   if ((event->modifier_state & CLUTTER_CONTROL_MASK)
781       && (event->keyval == CLUTTER_KEY_v || event->keyval == CLUTTER_KEY_V))
782     {
783       StClipboard *clipboard;
784 
785       clipboard = st_clipboard_get_default ();
786 
787       st_clipboard_get_text (clipboard,
788                              ST_CLIPBOARD_TYPE_CLIPBOARD,
789                              st_entry_clipboard_callback,
790                              actor);
791 
792       return TRUE;
793     }
794 
795   /* copy */
796   if ((event->modifier_state & CLUTTER_CONTROL_MASK)
797       && (event->keyval == CLUTTER_KEY_c || event->keyval == CLUTTER_KEY_C))
798     {
799       StClipboard *clipboard;
800       gchar *text;
801 
802       clipboard = st_clipboard_get_default ();
803 
804       text = clutter_text_get_selection ((ClutterText*) priv->entry);
805 
806       if (text && strlen (text))
807         st_clipboard_set_text (clipboard,
808                                ST_CLIPBOARD_TYPE_CLIPBOARD,
809                                text);
810 
811       return TRUE;
812     }
813 
814 
815   /* cut */
816   if ((event->modifier_state & CLUTTER_CONTROL_MASK)
817       && (event->keyval == CLUTTER_KEY_x || event->keyval == CLUTTER_KEY_X))
818     {
819       StClipboard *clipboard;
820       gchar *text;
821 
822       clipboard = st_clipboard_get_default ();
823 
824       text = clutter_text_get_selection ((ClutterText*) priv->entry);
825 
826       if (text && strlen (text))
827         {
828           st_clipboard_set_text (clipboard,ST_CLIPBOARD_TYPE_CLIPBOARD, text);
829 
830           /* now delete the text */
831           clutter_text_delete_selection ((ClutterText *) priv->entry);
832         }
833 
834       return TRUE;
835     }
836 
837   return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->key_press_event (actor, event);
838 }
839 
840 static void
st_entry_key_focus_in(ClutterActor * actor)841 st_entry_key_focus_in (ClutterActor *actor)
842 {
843   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
844 
845   /* We never want key focus. The ClutterText should be given first
846      pass for all key events */
847   clutter_actor_grab_key_focus (priv->entry);
848 }
849 
850 static void
st_entry_class_init(StEntryClass * klass)851 st_entry_class_init (StEntryClass *klass)
852 {
853   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
854   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
855   StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
856   GParamSpec *pspec;
857 
858   gobject_class->set_property = st_entry_set_property;
859   gobject_class->get_property = st_entry_get_property;
860   gobject_class->finalize = st_entry_finalize;
861   gobject_class->dispose = st_entry_dispose;
862 
863   actor_class->get_preferred_width = st_entry_get_preferred_width;
864   actor_class->get_preferred_height = st_entry_get_preferred_height;
865   actor_class->allocate = st_entry_allocate;
866 
867   actor_class->key_press_event = st_entry_key_press_event;
868   actor_class->key_focus_in = st_entry_key_focus_in;
869 
870   widget_class->style_changed = st_entry_style_changed;
871   widget_class->navigate_focus = st_entry_navigate_focus;
872   widget_class->get_accessible_type = st_entry_accessible_get_type;
873 
874   pspec = g_param_spec_object ("clutter-text",
875              "Clutter Text",
876              "Internal ClutterText actor",
877              CLUTTER_TYPE_TEXT,
878              G_PARAM_READABLE);
879   g_object_class_install_property (gobject_class, PROP_CLUTTER_TEXT, pspec);
880 
881   pspec = g_param_spec_string ("hint-text",
882                                "Hint Text",
883                                "Text to display when the entry is not focused "
884                                "and the text property is empty",
885                                NULL, G_PARAM_READWRITE);
886   g_object_class_install_property (gobject_class, PROP_HINT_TEXT, pspec);
887 
888   pspec = g_param_spec_string ("text",
889                                "Text",
890                                "Text of the entry",
891                                NULL, G_PARAM_READWRITE);
892   g_object_class_install_property (gobject_class, PROP_TEXT, pspec);
893 
894   /* signals */
895   /**
896    * StEntry::primary-icon-clicked:
897    *
898    * Emitted when the primary icon is clicked
899    */
900   entry_signals[PRIMARY_ICON_CLICKED] =
901     g_signal_new ("primary-icon-clicked",
902                   G_TYPE_FROM_CLASS (klass),
903                   G_SIGNAL_RUN_LAST,
904                   G_STRUCT_OFFSET (StEntryClass, primary_icon_clicked),
905                   NULL, NULL, NULL,
906                   G_TYPE_NONE, 0);
907   /**
908    * StEntry::secondary-icon-clicked:
909    *
910    * Emitted when the secondary icon is clicked
911    */
912   entry_signals[SECONDARY_ICON_CLICKED] =
913     g_signal_new ("secondary-icon-clicked",
914                   G_TYPE_FROM_CLASS (klass),
915                   G_SIGNAL_RUN_LAST,
916                   G_STRUCT_OFFSET (StEntryClass, secondary_icon_clicked),
917                   NULL, NULL, NULL,
918                   G_TYPE_NONE, 0);
919 }
920 
921 static void
st_entry_init(StEntry * entry)922 st_entry_init (StEntry *entry)
923 {
924   StEntryPrivate *priv;
925 
926   priv = entry->priv = st_entry_get_instance_private (entry);
927 
928   priv->entry = g_object_new (ST_TYPE_IM_TEXT,
929                               "line-alignment", PANGO_ALIGN_LEFT,
930                               "editable", TRUE,
931                               "reactive", TRUE,
932                               "single-line-mode", TRUE,
933                               NULL);
934 
935   g_signal_connect (priv->entry, "key-focus-in",
936                     G_CALLBACK (clutter_text_focus_in_cb), entry);
937 
938   g_signal_connect (priv->entry, "key-focus-out",
939                     G_CALLBACK (clutter_text_focus_out_cb), entry);
940 
941   g_signal_connect (priv->entry, "notify::password-char",
942                     G_CALLBACK (clutter_text_password_char_cb), entry);
943 
944   g_signal_connect (priv->entry, "button-press-event",
945                     G_CALLBACK (clutter_text_button_press_event), entry);
946 
947   g_signal_connect (priv->entry, "notify::selection-bound",
948                     G_CALLBACK (clutter_text_selection_bound_cb), entry);
949 
950   g_signal_connect (priv->entry, "cursor-changed",
951                     G_CALLBACK (clutter_text_cursor_changed), entry);
952 
953   priv->spacing = 6.0f;
954 
955   clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->entry);
956   clutter_actor_set_reactive ((ClutterActor *) entry, TRUE);
957 
958   /* set cursor hidden until we receive focus */
959   priv->blink_timeout = 0;
960   priv->blink_time = 0;
961   priv->cursor_visible = FALSE;
962   clutter_text_set_cursor_visible ((ClutterText *) priv->entry, FALSE);
963 }
964 
965 /**
966  * st_entry_new:
967  * @text: text to set the entry to
968  *
969  * Create a new #StEntry with the specified entry
970  *
971  * Returns: a new #StEntry
972  */
973 StWidget *
st_entry_new(const gchar * text)974 st_entry_new (const gchar *text)
975 {
976   StWidget *entry;
977 
978   /* add the entry to the stage, but don't allow it to be visible */
979   entry = g_object_new (ST_TYPE_ENTRY,
980                         "text", text,
981                         NULL);
982 
983   return entry;
984 }
985 
986 /**
987  * st_entry_get_text:
988  * @entry: a #StEntry
989  *
990  * Get the text displayed on the entry
991  *
992  * Returns: the text for the entry. This must not be freed by the application
993  */
994 const gchar *
st_entry_get_text(StEntry * entry)995 st_entry_get_text (StEntry *entry)
996 {
997   g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
998 
999   if (entry->priv->hint_visible)
1000     return "";
1001   else
1002     return clutter_text_get_text (CLUTTER_TEXT (entry->priv->entry));
1003 }
1004 
1005 /**
1006  * st_entry_set_text:
1007  * @entry: a #StEntry
1008  * @text: (allow-none): text to set the entry to
1009  *
1010  * Sets the text displayed on the entry
1011  */
1012 void
st_entry_set_text(StEntry * entry,const gchar * text)1013 st_entry_set_text (StEntry     *entry,
1014                    const gchar *text)
1015 {
1016   StEntryPrivate *priv;
1017 
1018   g_return_if_fail (ST_IS_ENTRY (entry));
1019 
1020   priv = entry->priv;
1021 
1022   /* set a hint if we are blanking the entry */
1023   if (priv->hint
1024       && text && !strcmp ("", text)
1025       && !HAS_FOCUS (priv->entry))
1026     {
1027       text = priv->hint;
1028       priv->hint_visible = TRUE;
1029       st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
1030     }
1031   else
1032     {
1033       st_widget_remove_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
1034 
1035       priv->hint_visible = FALSE;
1036     }
1037 
1038   clutter_text_set_text (CLUTTER_TEXT (priv->entry), text);
1039 
1040   g_object_notify (G_OBJECT (entry), "text");
1041 }
1042 
1043 /**
1044  * st_entry_get_clutter_text:
1045  * @entry: a #StEntry
1046  *
1047  * Retrieve the internal #ClutterText so that extra parameters can be set
1048  *
1049  * Returns: (transfer none): the #ClutterText used by #StEntry. The entry is
1050  * owned by the #StEntry and should not be unref'ed by the application.
1051  */
1052 ClutterActor*
st_entry_get_clutter_text(StEntry * entry)1053 st_entry_get_clutter_text (StEntry *entry)
1054 {
1055   g_return_val_if_fail (ST_ENTRY (entry), NULL);
1056 
1057   return entry->priv->entry;
1058 }
1059 
1060 /**
1061  * st_entry_set_hint_text:
1062  * @entry: a #StEntry
1063  * @text: (allow-none): text to set as the entry hint
1064  *
1065  * Sets the text to display when the entry is empty and unfocused. When the
1066  * entry is displaying the hint, it has a pseudo class of "indeterminate".
1067  * A value of NULL unsets the hint.
1068  */
1069 void
st_entry_set_hint_text(StEntry * entry,const gchar * text)1070 st_entry_set_hint_text (StEntry     *entry,
1071                         const gchar *text)
1072 {
1073   StEntryPrivate *priv;
1074 
1075   g_return_if_fail (ST_IS_ENTRY (entry));
1076 
1077   priv = entry->priv;
1078 
1079   g_free (priv->hint);
1080 
1081   priv->hint = g_strdup (text);
1082 
1083   if (!strcmp (clutter_text_get_text (CLUTTER_TEXT (priv->entry)), "")
1084       && !HAS_FOCUS (priv->entry))
1085     {
1086       priv->hint_visible = TRUE;
1087 
1088       clutter_text_set_text (CLUTTER_TEXT (priv->entry), priv->hint);
1089       st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
1090     }
1091 }
1092 
1093 /**
1094  * st_entry_get_hint_text:
1095  * @entry: a #StEntry
1096  *
1097  * Gets the text that is displayed when the entry is empty and unfocused
1098  *
1099  * Returns: the current value of the hint property. This string is owned by the
1100  * #StEntry and should not be freed or modified.
1101  */
1102 const gchar *
st_entry_get_hint_text(StEntry * entry)1103 st_entry_get_hint_text (StEntry *entry)
1104 {
1105   g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
1106 
1107   return entry->priv->hint;
1108 }
1109 
1110 static gboolean
_st_entry_icon_press_cb(ClutterActor * actor,ClutterButtonEvent * event,StEntry * entry)1111 _st_entry_icon_press_cb (ClutterActor       *actor,
1112                          ClutterButtonEvent *event,
1113                          StEntry            *entry)
1114 {
1115   StEntryPrivate *priv = entry->priv;
1116 
1117   if (actor == priv->primary_icon)
1118     g_signal_emit (entry, entry_signals[PRIMARY_ICON_CLICKED], 0);
1119   else
1120     g_signal_emit (entry, entry_signals[SECONDARY_ICON_CLICKED], 0);
1121 
1122   return FALSE;
1123 }
1124 
1125 static void
_st_entry_set_icon(StEntry * entry,ClutterActor ** icon,ClutterActor * new_icon)1126 _st_entry_set_icon (StEntry       *entry,
1127                     ClutterActor **icon,
1128                     ClutterActor  *new_icon)
1129 {
1130   if (*icon)
1131     {
1132       g_signal_handlers_disconnect_by_func (*icon,
1133                                             _st_entry_icon_press_cb,
1134                                             entry);
1135       clutter_actor_remove_child (CLUTTER_ACTOR (entry), *icon);
1136       *icon = NULL;
1137     }
1138 
1139   if (new_icon)
1140     {
1141       *icon = g_object_ref (new_icon);
1142 
1143       clutter_actor_set_reactive (*icon, TRUE);
1144       clutter_actor_add_child (CLUTTER_ACTOR (entry), *icon);
1145       g_signal_connect (*icon, "button-release-event",
1146                         G_CALLBACK (_st_entry_icon_press_cb), entry);
1147     }
1148 
1149   clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
1150 }
1151 
1152 static void
_st_entry_set_icon_from_file(StEntry * entry,ClutterActor ** icon,const gchar * filename)1153 _st_entry_set_icon_from_file (StEntry       *entry,
1154                               ClutterActor **icon,
1155                               const gchar   *filename)
1156 {
1157   ClutterActor *new_icon = NULL;
1158 
1159   if (filename)
1160     {
1161       StTextureCache *cache;
1162       GFile *file;
1163       gchar *uri;
1164 
1165       cache = st_texture_cache_get_default ();
1166       file = g_file_new_for_path (filename);
1167       uri = g_file_get_uri (file);
1168 
1169       g_object_unref (file);
1170 
1171       new_icon = (ClutterActor*) st_texture_cache_load_uri_async (cache, uri, -1, -1);
1172       g_free (uri);
1173     }
1174 
1175   _st_entry_set_icon  (entry, icon, new_icon);
1176 }
1177 
1178 /**
1179  * st_entry_set_primary_icon:
1180  * @entry: a #StEntry
1181  * @icon: (allow-none): a #ClutterActor
1182  *
1183  * Set the primary icon of the entry to @icon
1184  */
1185 void
st_entry_set_primary_icon(StEntry * entry,ClutterActor * icon)1186 st_entry_set_primary_icon (StEntry      *entry,
1187                            ClutterActor *icon)
1188 {
1189   StEntryPrivate *priv;
1190 
1191   g_return_if_fail (ST_IS_ENTRY (entry));
1192 
1193   priv = entry->priv;
1194 
1195   _st_entry_set_icon (entry, &priv->primary_icon, icon);
1196 }
1197 
1198 /**
1199  * st_entry_set_secondary_icon:
1200  * @entry: a #StEntry
1201  * @icon: (allow-none): an #ClutterActor
1202  *
1203  * Set the secondary icon of the entry to @icon
1204  */
1205 void
st_entry_set_secondary_icon(StEntry * entry,ClutterActor * icon)1206 st_entry_set_secondary_icon (StEntry      *entry,
1207                              ClutterActor *icon)
1208 {
1209   StEntryPrivate *priv;
1210 
1211   g_return_if_fail (ST_IS_ENTRY (entry));
1212 
1213   priv = entry->priv;
1214 
1215   _st_entry_set_icon (entry, &priv->secondary_icon, icon);
1216 }
1217 
1218 /**
1219  * st_entry_set_primary_icon_from_file:
1220  * @entry: a #StEntry
1221  * @filename: (allow-none): filename of an icon
1222  *
1223  * Set the primary icon of the entry to the given filename
1224  */
1225 void
st_entry_set_primary_icon_from_file(StEntry * entry,const gchar * filename)1226 st_entry_set_primary_icon_from_file (StEntry     *entry,
1227                                      const gchar *filename)
1228 {
1229   StEntryPrivate *priv;
1230 
1231   g_return_if_fail (ST_IS_ENTRY (entry));
1232 
1233   priv = entry->priv;
1234 
1235   _st_entry_set_icon_from_file (entry, &priv->primary_icon, filename);
1236 
1237 }
1238 
1239 /**
1240  * st_entry_set_secondary_icon_from_file:
1241  * @entry: a #StEntry
1242  * @filename: (allow-none): filename of an icon
1243  *
1244  * Set the primary icon of the entry to the given filename
1245  */
1246 void
st_entry_set_secondary_icon_from_file(StEntry * entry,const gchar * filename)1247 st_entry_set_secondary_icon_from_file (StEntry     *entry,
1248                                        const gchar *filename)
1249 {
1250   StEntryPrivate *priv;
1251 
1252   g_return_if_fail (ST_IS_ENTRY (entry));
1253 
1254   priv = entry->priv;
1255 
1256   _st_entry_set_icon_from_file (entry, &priv->secondary_icon, filename);
1257 
1258 }
1259 
1260 /******************************************************************************/
1261 /*************************** ACCESSIBILITY SUPPORT ****************************/
1262 /******************************************************************************/
1263 
1264 #define ST_TYPE_ENTRY_ACCESSIBLE         (st_entry_accessible_get_type ())
1265 #define ST_ENTRY_ACCESSIBLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessible))
1266 #define ST_IS_ENTRY_ACCESSIBLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), ST_TYPE_ENTRY_ACCESSIBLE))
1267 #define ST_ENTRY_ACCESSIBLE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
1268 #define ST_IS_ENTRY_ACCESSIBLE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    ST_TYPE_ENTRY_ACCESSIBLE))
1269 #define ST_ENTRY_ACCESSIBLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
1270 
1271 typedef struct _StEntryAccessible  StEntryAccessible;
1272 typedef struct _StEntryAccessibleClass  StEntryAccessibleClass;
1273 
1274 struct _StEntryAccessible
1275 {
1276   StWidgetAccessible parent;
1277 };
1278 
1279 struct _StEntryAccessibleClass
1280 {
1281   StWidgetAccessibleClass parent_class;
1282 };
1283 
G_DEFINE_TYPE(StEntryAccessible,st_entry_accessible,ST_TYPE_WIDGET_ACCESSIBLE)1284 G_DEFINE_TYPE (StEntryAccessible, st_entry_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
1285 
1286 static void
1287 st_entry_accessible_init (StEntryAccessible *self)
1288 {
1289   /* initialization done on AtkObject->initialize */
1290 }
1291 
1292 static void
st_entry_accessible_initialize(AtkObject * obj,gpointer data)1293 st_entry_accessible_initialize (AtkObject *obj,
1294                                 gpointer   data)
1295 {
1296   ATK_OBJECT_CLASS (st_entry_accessible_parent_class)->initialize (obj, data);
1297 
1298   /* StEntry is behaving as a StImText container */
1299   atk_object_set_role (obj, ATK_ROLE_PANEL);
1300 }
1301 
1302 static gint
st_entry_accessible_get_n_children(AtkObject * obj)1303 st_entry_accessible_get_n_children (AtkObject *obj)
1304 {
1305   StEntry *entry = NULL;
1306 
1307   g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), 0);
1308 
1309   entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
1310 
1311   if (entry == NULL)
1312     return 0;
1313 
1314   if (entry->priv->entry == NULL)
1315     return 0;
1316   else
1317     return 1;
1318 }
1319 
1320 static AtkObject*
st_entry_accessible_ref_child(AtkObject * obj,gint i)1321 st_entry_accessible_ref_child (AtkObject *obj,
1322                                gint       i)
1323 {
1324   StEntry *entry = NULL;
1325   AtkObject *result = NULL;
1326 
1327   g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), NULL);
1328   g_return_val_if_fail (i == 0, NULL);
1329 
1330   entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
1331 
1332   if (entry == NULL)
1333     return NULL;
1334 
1335   if (entry->priv->entry == NULL)
1336     return NULL;
1337 
1338   result = clutter_actor_get_accessible (entry->priv->entry);
1339   g_object_ref (result);
1340 
1341   return result;
1342 }
1343 
1344 
1345 static void
st_entry_accessible_class_init(StEntryAccessibleClass * klass)1346 st_entry_accessible_class_init (StEntryAccessibleClass *klass)
1347 {
1348   AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
1349 
1350   atk_class->initialize = st_entry_accessible_initialize;
1351   atk_class->get_n_children = st_entry_accessible_get_n_children;
1352   atk_class->ref_child= st_entry_accessible_ref_child;
1353 }
1354