text: Enable implicit color animations
[profile/ivi/clutter.git] / clutter / clutter-text.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2008  Intel Corporation.
7  *
8  * Authored By: Øyvind Kolås <pippin@o-hand.com>
9  *              Emmanuele Bassi <ebassi@linux.intel.com>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
23  */
24
25 /**
26  * SECTION:clutter-text
27  * @short_description: An actor for displaying and editing text
28  *
29  * #ClutterText is an actor that displays custom text using Pango
30  * as the text rendering engine.
31  *
32  * #ClutterText also allows inline editing of the text if the
33  * actor is set editable using clutter_text_set_editable().
34  *
35  * Selection using keyboard or pointers can be enabled using
36  * clutter_text_set_selectable().
37  *
38  * #ClutterText is available since Clutter 1.0
39  */
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44
45 #include <string.h>
46 #include <math.h>
47
48 #include "clutter-text.h"
49
50 #include "clutter-actor-private.h"
51 #include "clutter-animatable.h"
52 #include "clutter-binding-pool.h"
53 #include "clutter-color.h"
54 #include "clutter-debug.h"
55 #include "clutter-enum-types.h"
56 #include "clutter-keysyms.h"
57 #include "clutter-main.h"
58 #include "clutter-marshal.h"
59 #include "clutter-private.h"    /* includes <cogl-pango/cogl-pango.h> */
60 #include "clutter-profile.h"
61 #include "clutter-property-transition.h"
62 #include "clutter-text-buffer.h"
63 #include "clutter-units.h"
64 #include "clutter-paint-volume-private.h"
65 #include "clutter-scriptable.h"
66
67 /* cursor width in pixels */
68 #define DEFAULT_CURSOR_SIZE     2
69
70 /* We need at least three cached layouts to run the allocation without
71  * regenerating a new layout. First the layout will be generated at
72  * full width to get the preferred width, then it will be generated at
73  * the preferred width to get the preferred height and then it might
74  * be regenerated at a different width to get the height for the
75  * actual allocated width
76  *
77  * since we might get multiple queries from layout managers doing a
78  * double-pass allocations, like tabular ones, we should use 6 slots
79  */
80 #define N_CACHED_LAYOUTS        6
81
82 #define CLUTTER_TEXT_GET_PRIVATE(obj)   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXT, ClutterTextPrivate))
83
84 typedef struct _LayoutCache     LayoutCache;
85
86 static const ClutterColor default_cursor_color    = {   0,   0,   0, 255 };
87 static const ClutterColor default_selection_color = {   0,   0,   0, 255 };
88 static const ClutterColor default_text_color      = {   0,   0,   0, 255 };
89 static const ClutterColor default_selected_text_color = {   0,   0,   0, 255 };
90
91 static ClutterAnimatableIface *parent_animatable_iface = NULL;
92
93 static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
94 static void clutter_animatable_iface_init (ClutterAnimatableIface *iface);
95
96 G_DEFINE_TYPE_WITH_CODE (ClutterText,
97                          clutter_text,
98                          CLUTTER_TYPE_ACTOR,
99                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
100                                                 clutter_scriptable_iface_init)
101                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_ANIMATABLE,
102                                                 clutter_animatable_iface_init));
103
104 struct _LayoutCache
105 {
106   /* Cached layout. Pango internally caches the computed extents
107    * when they are requested so there is no need to cache that as
108    * well
109    */
110   PangoLayout *layout;
111
112   /* A number representing the age of this cache (so that when a
113    * new layout is needed the last used cache is replaced)
114    */
115   guint age;
116 };
117
118 struct _ClutterTextPrivate
119 {
120   PangoFontDescription *font_desc;
121
122   /* the displayed text */
123   ClutterTextBuffer *buffer;
124
125   gchar *font_name;
126
127   gchar *preedit_str;
128
129   ClutterColor text_color;
130
131   LayoutCache cached_layouts[N_CACHED_LAYOUTS];
132   guint cache_age;
133
134   /* These are the attributes set by the attributes property */
135   PangoAttrList *attrs;
136   /* These are the attributes derived from the text when the
137      use-markup property is set */
138   PangoAttrList *markup_attrs;
139   /* This is the combination of the above two lists. It is set to NULL
140      whenever either of them changes and then regenerated by merging
141      the two lists whenever a layout is needed */
142   PangoAttrList *effective_attrs;
143   /* These are the attributes for the preedit string. These are merged
144      with the effective attributes into a temporary list before
145      creating a layout */
146   PangoAttrList *preedit_attrs;
147
148   /* current cursor position */
149   gint position;
150
151   /* current 'other end of selection' position */
152   gint selection_bound;
153
154   /* the x position in the PangoLayout, used to
155    * avoid drifting when repeatedly moving up|down
156    */
157   gint x_pos;
158
159   /* the x position of the PangoLayout when in
160    * single line mode, to scroll the contents of the
161    * text actor
162    */
163   gint text_x;
164
165   /* the y position of the PangoLayout, fixed to 0 by
166    * default for now */
167   gint text_y;
168
169   /* Where to draw the cursor */
170   ClutterGeometry cursor_pos;
171   ClutterColor cursor_color;
172   guint cursor_size;
173
174   /* Box representing the paint volume. The box is lazily calculated
175      and cached */
176   ClutterPaintVolume paint_volume;
177
178   guint preedit_cursor_pos;
179   gint preedit_n_chars;
180
181   ClutterColor selection_color;
182
183   ClutterColor selected_text_color;
184
185   gunichar password_char;
186
187   guint password_hint_id;
188   guint password_hint_timeout;
189
190   /* Signal handler for when the backend changes its font settings */
191   guint settings_changed_id;
192
193   /* Signal handler for when the :text-direction changes */
194   guint direction_changed_id;
195
196   /* bitfields */
197   guint alignment               : 2;
198   guint wrap                    : 1;
199   guint use_underline           : 1;
200   guint use_markup              : 1;
201   guint ellipsize               : 3;
202   guint single_line_mode        : 1;
203   guint wrap_mode               : 3;
204   guint justify                 : 1;
205   guint editable                : 1;
206   guint cursor_visible          : 1;
207   guint activatable             : 1;
208   guint selectable              : 1;
209   guint selection_color_set     : 1;
210   guint in_select_drag          : 1;
211   guint cursor_color_set        : 1;
212   guint preedit_set             : 1;
213   guint is_default_font         : 1;
214   guint has_focus               : 1;
215   guint selected_text_color_set : 1;
216   guint paint_volume_valid      : 1;
217   guint show_password_hint      : 1;
218   guint password_hint_visible   : 1;
219 };
220
221 enum
222 {
223   PROP_0,
224
225   PROP_BUFFER,
226   PROP_FONT_NAME,
227   PROP_FONT_DESCRIPTION,
228   PROP_TEXT,
229   PROP_COLOR,
230   PROP_USE_MARKUP,
231   PROP_ATTRIBUTES,
232   PROP_LINE_ALIGNMENT,
233   PROP_LINE_WRAP,
234   PROP_LINE_WRAP_MODE,
235   PROP_JUSTIFY,
236   PROP_ELLIPSIZE,
237   PROP_POSITION,
238   PROP_SELECTION_BOUND,
239   PROP_SELECTION_COLOR,
240   PROP_SELECTION_COLOR_SET,
241   PROP_CURSOR_VISIBLE,
242   PROP_CURSOR_COLOR,
243   PROP_CURSOR_COLOR_SET,
244   PROP_CURSOR_SIZE,
245   PROP_EDITABLE,
246   PROP_SELECTABLE,
247   PROP_ACTIVATABLE,
248   PROP_PASSWORD_CHAR,
249   PROP_MAX_LENGTH,
250   PROP_SINGLE_LINE_MODE,
251   PROP_SELECTED_TEXT_COLOR,
252   PROP_SELECTED_TEXT_COLOR_SET,
253
254   PROP_LAST
255 };
256
257 static GParamSpec *obj_props[PROP_LAST];
258
259 enum
260 {
261   TEXT_CHANGED,
262   CURSOR_EVENT,
263   ACTIVATE,
264   INSERT_TEXT,
265   DELETE_TEXT,
266
267   LAST_SIGNAL
268 };
269
270 static guint text_signals[LAST_SIGNAL] = { 0, };
271
272 static void clutter_text_settings_changed_cb (ClutterText *text);
273 static void buffer_connect_signals (ClutterText *self);
274 static void buffer_disconnect_signals (ClutterText *self);
275 static ClutterTextBuffer *get_buffer (ClutterText *self);
276
277 static inline void
278 clutter_text_dirty_paint_volume (ClutterText *text)
279 {
280   ClutterTextPrivate *priv = text->priv;
281
282   if (priv->paint_volume_valid)
283     {
284       clutter_paint_volume_free (&priv->paint_volume);
285       priv->paint_volume_valid = FALSE;
286     }
287 }
288
289 static inline void
290 clutter_text_queue_redraw (ClutterActor *self)
291 {
292   /* This is a wrapper for clutter_actor_queue_redraw that also
293      dirties the cached paint volume. It would be nice if we could
294      just override the default implementation of the queue redraw
295      signal to do this instead but that doesn't work because the
296      signal isn't immediately emitted when queue_redraw is called.
297      Clutter will however immediately call get_paint_volume when
298      queue_redraw is called so we do need to dirty it immediately. */
299
300   clutter_text_dirty_paint_volume (CLUTTER_TEXT (self));
301
302   clutter_actor_queue_redraw (self);
303 }
304
305 #define clutter_actor_queue_redraw \
306   Please_use_clutter_text_queue_redraw_instead
307
308 #define offset_real(t,p)        ((p) == -1 ? g_utf8_strlen ((t), -1) : (p))
309
310 static gint
311 offset_to_bytes (const gchar *text,
312                  gint         pos)
313 {
314   const gchar *ptr;
315
316   if (pos < 0)
317     return strlen (text);
318
319   /* Loop over each character in the string until we either reach the
320      end or the requested position */
321   for (ptr = text; *ptr && pos-- > 0; ptr = g_utf8_next_char (ptr));
322
323   return ptr - text;
324 }
325
326 #define bytes_to_offset(t,p)    (g_utf8_pointer_to_offset ((t), (t) + (p)))
327
328 static inline void
329 clutter_text_clear_selection (ClutterText *self)
330 {
331   ClutterTextPrivate *priv = self->priv;
332
333   if (priv->selection_bound != priv->position)
334     {
335       priv->selection_bound = priv->position;
336       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
337       clutter_text_queue_redraw (CLUTTER_ACTOR (self));
338     }
339 }
340
341 static gchar *
342 clutter_text_get_display_text (ClutterText *self)
343 {
344   ClutterTextPrivate *priv = self->priv;
345   ClutterTextBuffer *buffer;
346   const gchar *text;
347
348   buffer = get_buffer (self);
349   text = clutter_text_buffer_get_text (buffer);
350
351   /* simple short-circuit to avoid going through GString
352    * with an empty text and a password char set
353    */
354   if (text[0] == '\0')
355     return g_strdup ("");
356
357   if (G_LIKELY (priv->password_char == 0))
358     return g_strdup (text);
359   else
360     {
361       GString *str;
362       gunichar invisible_char;
363       gchar buf[7];
364       gint char_len, i;
365       guint n_chars;
366
367       n_chars = clutter_text_buffer_get_length (buffer);
368       str = g_string_sized_new (clutter_text_buffer_get_bytes (buffer));
369       invisible_char = priv->password_char;
370
371       /* we need to convert the string built of invisible
372        * characters into UTF-8 for it to be fed to the Pango
373        * layout
374        */
375       memset (buf, 0, sizeof (buf));
376       char_len = g_unichar_to_utf8 (invisible_char, buf);
377
378       if (priv->show_password_hint && priv->password_hint_visible)
379         {
380           char *last_char;
381
382           for (i = 0; i < n_chars - 1; i++)
383             g_string_append_len (str, buf, char_len);
384
385           last_char = g_utf8_offset_to_pointer (text, n_chars - 1);
386           g_string_append (str, last_char);
387         }
388       else
389         {
390           for (i = 0; i < n_chars; i++)
391             g_string_append_len (str, buf, char_len);
392         }
393
394       return g_string_free (str, FALSE);
395     }
396 }
397
398 static inline void
399 clutter_text_ensure_effective_attributes (ClutterText *self)
400 {
401   ClutterTextPrivate *priv = self->priv;
402
403   /* If we already have the effective attributes then we don't need to
404      do anything */
405   if (priv->effective_attrs != NULL)
406     return;
407
408   /* same as if we don't have any attribute at all */
409   if (priv->attrs == NULL && priv->markup_attrs == NULL)
410     return;
411
412   if (priv->attrs != NULL)
413     {
414       /* If there are no markup attributes then we can just use
415          these attributes directly */
416       if (priv->markup_attrs == NULL)
417         priv->effective_attrs = pango_attr_list_ref (priv->attrs);
418       else
419         {
420           /* Otherwise we need to merge the two lists */
421           PangoAttrIterator *iter;
422           GSList *attributes, *l;
423
424           priv->effective_attrs = pango_attr_list_copy (priv->markup_attrs);
425
426           iter = pango_attr_list_get_iterator (priv->attrs);
427           do
428             {
429               attributes = pango_attr_iterator_get_attrs (iter);
430
431               for (l = attributes; l != NULL; l = l->next)
432                 {
433                   PangoAttribute *attr = l->data;
434
435                   pango_attr_list_insert (priv->effective_attrs, attr);
436                 }
437
438               g_slist_free (attributes);
439             }
440           while (pango_attr_iterator_next (iter));
441
442           pango_attr_iterator_destroy (iter);
443         }
444     }
445   else if (priv->markup_attrs != NULL)
446     {
447       /* We can just use the markup attributes directly */
448       priv->effective_attrs = pango_attr_list_ref (priv->markup_attrs);
449     }
450 }
451
452 static PangoLayout *
453 clutter_text_create_layout_no_cache (ClutterText       *text,
454                                      gint               width,
455                                      gint               height,
456                                      PangoEllipsizeMode ellipsize)
457 {
458   ClutterTextPrivate *priv = text->priv;
459   PangoLayout *layout;
460   gchar *contents;
461   gsize contents_len;
462
463   CLUTTER_STATIC_TIMER (text_layout_timer,
464                         "Mainloop",
465                         "Text Layout",
466                         "Layout creation",
467                         0);
468
469   CLUTTER_TIMER_START (_clutter_uprof_context, text_layout_timer);
470
471   layout = clutter_actor_create_pango_layout (CLUTTER_ACTOR (text), NULL);
472   pango_layout_set_font_description (layout, priv->font_desc);
473
474   contents = clutter_text_get_display_text (text);
475   contents_len = strlen (contents);
476
477   if (priv->editable && priv->preedit_set)
478     {
479       GString *tmp = g_string_new (contents);
480       PangoAttrList *tmp_attrs = pango_attr_list_new ();
481       gint cursor_index;
482
483       if (priv->position == 0)
484         cursor_index = 0;
485       else
486         cursor_index = offset_to_bytes (contents, priv->position);
487
488       g_string_insert (tmp, cursor_index, priv->preedit_str);
489
490       pango_layout_set_text (layout, tmp->str, tmp->len);
491
492       if (priv->preedit_attrs != NULL)
493         {
494           pango_attr_list_splice (tmp_attrs, priv->preedit_attrs,
495                                   cursor_index,
496                                   strlen (priv->preedit_str));
497
498           pango_layout_set_attributes (layout, tmp_attrs);
499         }
500
501       g_string_free (tmp, TRUE);
502       pango_attr_list_unref (tmp_attrs);
503     }
504   else
505     pango_layout_set_text (layout, contents, contents_len);
506
507   if (!priv->editable)
508     {
509       /* This will merge the markup attributes and the attributes
510          property if needed */
511       clutter_text_ensure_effective_attributes (text);
512
513       if (priv->effective_attrs != NULL)
514         pango_layout_set_attributes (layout, priv->effective_attrs);
515     }
516
517   pango_layout_set_alignment (layout, priv->alignment);
518   pango_layout_set_single_paragraph_mode (layout, priv->single_line_mode);
519   pango_layout_set_justify (layout, priv->justify);
520   pango_layout_set_wrap (layout, priv->wrap_mode);
521
522   pango_layout_set_ellipsize (layout, ellipsize);
523   pango_layout_set_width (layout, width);
524   pango_layout_set_height (layout, height);
525
526   g_free (contents);
527
528   CLUTTER_TIMER_STOP (_clutter_uprof_context, text_layout_timer);
529
530   return layout;
531 }
532
533 static void
534 clutter_text_dirty_cache (ClutterText *text)
535 {
536   ClutterTextPrivate *priv = text->priv;
537   int i;
538
539   /* Delete the cached layouts so they will be recreated the next time
540      they are needed */
541   for (i = 0; i < N_CACHED_LAYOUTS; i++)
542     if (priv->cached_layouts[i].layout)
543       {
544         g_object_unref (priv->cached_layouts[i].layout);
545         priv->cached_layouts[i].layout = NULL;
546       }
547
548   clutter_text_dirty_paint_volume (text);
549 }
550
551 /*
552  * clutter_text_set_font_description_internal:
553  * @self: a #ClutterText
554  * @desc: a #PangoFontDescription
555  *
556  * Sets @desc as the font description to be used by the #ClutterText
557  * actor. The font description ownership is transferred to @self so
558  * the #PangoFontDescription must not be freed after this function
559  *
560  * This function will also set the :font-name field as a side-effect
561  *
562  * This function will evict the layout cache, and queue a relayout if
563  * the #ClutterText actor has contents.
564  */
565 static inline void
566 clutter_text_set_font_description_internal (ClutterText          *self,
567                                             PangoFontDescription *desc)
568 {
569   ClutterTextPrivate *priv = self->priv;
570
571   if (priv->font_desc == desc)
572     return;
573
574   if (priv->font_desc != NULL)
575     pango_font_description_free (priv->font_desc);
576
577   priv->font_desc = desc;
578
579   /* update the font name string we use */
580   g_free (priv->font_name);
581   priv->font_name = pango_font_description_to_string (priv->font_desc);
582
583   clutter_text_dirty_cache (self);
584
585   if (clutter_text_buffer_get_length (get_buffer (self)) != 0)
586     clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
587
588   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_FONT_DESCRIPTION]);
589 }
590
591 static void
592 clutter_text_settings_changed_cb (ClutterText *text)
593 {
594   ClutterTextPrivate *priv = text->priv;
595   guint password_hint_time = 0;
596   ClutterSettings *settings;
597
598   settings = clutter_settings_get_default ();
599
600   g_object_get (settings, "password-hint-time", &password_hint_time, NULL);
601
602   priv->show_password_hint = password_hint_time > 0;
603   priv->password_hint_timeout = password_hint_time;
604
605   if (priv->is_default_font)
606     {
607       PangoFontDescription *font_desc;
608       gchar *font_name = NULL;
609
610       g_object_get (settings, "font-name", &font_name, NULL);
611
612       CLUTTER_NOTE (ACTOR, "Text[%p]: default font changed to '%s'",
613                     text,
614                     font_name);
615
616       font_desc = pango_font_description_from_string (font_name);
617       clutter_text_set_font_description_internal (text, font_desc);
618
619       g_free (font_name);
620     }
621
622   clutter_text_dirty_cache (text);
623   clutter_actor_queue_relayout (CLUTTER_ACTOR (text));
624 }
625
626 static void
627 clutter_text_direction_changed_cb (GObject    *gobject,
628                                    GParamSpec *pspec)
629 {
630   clutter_text_dirty_cache (CLUTTER_TEXT (gobject));
631
632   /* no need to queue a relayout: set_text_direction() will do that for us */
633 }
634
635 /*
636  * clutter_text_create_layout:
637  * @text: a #ClutterText
638  * @allocation_width: the allocation width
639  * @allocation_height: the allocation height
640  *
641  * Like clutter_text_create_layout_no_cache(), but will also ensure
642  * the glyphs cache. If a previously cached layout generated using the
643  * same width is available then that will be used instead of
644  * generating a new one.
645  */
646 static PangoLayout *
647 clutter_text_create_layout (ClutterText *text,
648                             gfloat       allocation_width,
649                             gfloat       allocation_height)
650 {
651   ClutterTextPrivate *priv = text->priv;
652   LayoutCache *oldest_cache = priv->cached_layouts;
653   gboolean found_free_cache = FALSE;
654   gint width = -1;
655   gint height = -1;
656   PangoEllipsizeMode ellipsize = PANGO_ELLIPSIZE_NONE;
657   int i;
658
659   CLUTTER_STATIC_COUNTER (text_cache_hit_counter,
660                           "Text layout cache hit counter",
661                           "Increments for each layout cache hit",
662                           0);
663   CLUTTER_STATIC_COUNTER (text_cache_miss_counter,
664                           "Text layout cache miss counter",
665                           "Increments for each layout cache miss",
666                           0);
667
668   /* First determine the width, height, and ellipsize mode that
669    * we need for the layout. The ellipsize mode depends on
670    * allocation_width/allocation_size as follows:
671    *
672    * Cases, assuming ellipsize != NONE on actor:
673    *
674    * Width request: ellipsization can be set or not on layout,
675    * doesn't matter.
676    *
677    * Height request: ellipsization must never be set on layout
678    * if wrap=true, because we need to measure the wrapped
679    * height. It must always be set if wrap=false.
680    *
681    * Allocate: ellipsization must always be set.
682    *
683    * See http://bugzilla.gnome.org/show_bug.cgi?id=560931
684    */
685
686   if (priv->ellipsize != PANGO_ELLIPSIZE_NONE)
687     {
688       if (allocation_height < 0 && priv->wrap)
689         ; /* must not set ellipsization on wrap=true height request */
690       else
691         {
692           if (!priv->editable)
693             ellipsize = priv->ellipsize;
694         }
695     }
696
697   /* When painting, we always need to set the width, since
698    * we might need to align to the right. When getting the
699    * height, however, there are some cases where we know that
700    * the width won't affect the width.
701    *
702    * - editable, single-line text actors, since those can
703    *   scroll the layout.
704    * - non-wrapping, non-ellipsizing actors.
705    */
706   if (allocation_width >= 0 &&
707       (allocation_height >= 0 ||
708        !((priv->editable && priv->single_line_mode) ||
709          (priv->ellipsize == PANGO_ELLIPSIZE_NONE && !priv->wrap))))
710     {
711       width = allocation_width * 1024 + 0.5f;
712     }
713
714   /* Pango only uses height if ellipsization is enabled, so don't set
715    * height if ellipsize isn't set. Pango implicitly enables wrapping
716    * if height is set, so don't set height if wrapping is disabled.
717    * In other words, only set height if we want to both wrap then
718    * ellipsize and we're not in single line mode.
719    *
720    * See http://bugzilla.gnome.org/show_bug.cgi?id=560931 if this
721    * seems odd.
722    */
723   if (allocation_height >= 0 &&
724       priv->wrap &&
725       priv->ellipsize != PANGO_ELLIPSIZE_NONE &&
726       !priv->single_line_mode)
727     {
728       height = allocation_height * 1024 + 0.5f;
729     }
730
731   /* Search for a cached layout with the same width and keep
732    * track of the oldest one
733    */
734   for (i = 0; i < N_CACHED_LAYOUTS; i++)
735     {
736       if (priv->cached_layouts[i].layout == NULL)
737         {
738           /* Always prefer free cache spaces */
739           found_free_cache = TRUE;
740           oldest_cache = priv->cached_layouts + i;
741         }
742       else
743         {
744           PangoLayout *cached = priv->cached_layouts[i].layout;
745           gint cached_width = pango_layout_get_width (cached);
746           gint cached_height = pango_layout_get_height (cached);
747           gint cached_ellipsize = pango_layout_get_ellipsize (cached);
748
749           if (cached_width == width &&
750               cached_height == height &&
751               cached_ellipsize == ellipsize)
752             {
753               /* If this cached layout is using the same size then we can
754                * just return that directly
755                */
756               CLUTTER_NOTE (ACTOR,
757                             "ClutterText: %p: cache hit for size %.2fx%.2f",
758                             text,
759                             allocation_width,
760                             allocation_height);
761
762               CLUTTER_COUNTER_INC (_clutter_uprof_context,
763                                    text_cache_hit_counter);
764
765               return priv->cached_layouts[i].layout;
766             }
767
768           /* When getting the preferred height for a specific width,
769            * we might be able to reuse the layout from getting the
770            * preferred width. If the width that the layout gives
771            * unconstrained is less than the width that we are using
772            * than the height will be unaffected by that width.
773            */
774           if (allocation_height < 0 &&
775               cached_width == -1 &&
776               cached_ellipsize == ellipsize)
777             {
778               PangoRectangle logical_rect;
779
780               pango_layout_get_extents (priv->cached_layouts[i].layout,
781                                         NULL,
782                                         &logical_rect);
783
784               if (logical_rect.width <= width)
785                 {
786                   /* We've been asked for our height for the width we gave as a result
787                    * of a _get_preferred_width call
788                    */
789                   CLUTTER_NOTE (ACTOR,
790                                 "ClutterText: %p: cache hit for size %.2fx%.2f "
791                                 "(unwrapped width narrower than given width)",
792                                 text,
793                                 allocation_width,
794                                 allocation_height);
795
796                   CLUTTER_COUNTER_INC (_clutter_uprof_context,
797                                        text_cache_hit_counter);
798
799                   return priv->cached_layouts[i].layout;
800                 }
801             }
802
803           if (!found_free_cache &&
804               (priv->cached_layouts[i].age < oldest_cache->age))
805             {
806               oldest_cache = priv->cached_layouts + i;
807             }
808         }
809     }
810
811   CLUTTER_NOTE (ACTOR, "ClutterText: %p: cache miss for size %.2fx%.2f",
812                 text,
813                 allocation_width,
814                 allocation_height);
815
816   CLUTTER_COUNTER_INC (_clutter_uprof_context, text_cache_miss_counter);
817
818   /* If we make it here then we didn't have a cached version so we
819      need to recreate the layout */
820   if (oldest_cache->layout)
821     g_object_unref (oldest_cache->layout);
822
823   oldest_cache->layout =
824     clutter_text_create_layout_no_cache (text, width, height, ellipsize);
825
826   cogl_pango_ensure_glyph_cache_for_layout (oldest_cache->layout);
827
828   /* Mark the 'time' this cache was created and advance the time */
829   oldest_cache->age = priv->cache_age++;
830   return oldest_cache->layout;
831 }
832
833 /**
834  * clutter_text_coords_to_position:
835  * @self: a #ClutterText
836  * @x: the X coordinate, relative to the actor
837  * @y: the Y coordinate, relative to the actor
838  *
839  * Retrieves the position of the character at the given coordinates.
840  *
841  * Return: the position of the character
842  *
843  * Since: 1.10
844  */
845 gint
846 clutter_text_coords_to_position (ClutterText *self,
847                                  gfloat       x,
848                                  gfloat       y)
849 {
850   gint index_;
851   gint px, py;
852   gint trailing;
853
854   g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0);
855
856   /* Take any offset due to scrolling into account, and normalize
857    * the coordinates to PangoScale units
858    */
859   px = (x - self->priv->text_x) * PANGO_SCALE;
860   py = (y - self->priv->text_y) * PANGO_SCALE;
861
862   pango_layout_xy_to_index (clutter_text_get_layout (self),
863                             px, py,
864                             &index_, &trailing);
865
866   return index_ + trailing;
867 }
868
869 /**
870  * clutter_text_position_to_coords:
871  * @self: a #ClutterText
872  * @position: position in characters
873  * @x: (out): return location for the X coordinate, or %NULL
874  * @y: (out): return location for the Y coordinate, or %NULL
875  * @line_height: (out): return location for the line height, or %NULL
876  *
877  * Retrieves the coordinates of the given @position.
878  *
879  * Return value: %TRUE if the conversion was successful
880  *
881  * Since: 1.0
882  */
883 gboolean
884 clutter_text_position_to_coords (ClutterText *self,
885                                  gint         position,
886                                  gfloat      *x,
887                                  gfloat      *y,
888                                  gfloat      *line_height)
889 {
890   ClutterTextPrivate *priv;
891   PangoRectangle rect;
892   gint n_chars;
893   gint password_char_bytes = 1;
894   gint index_;
895   gsize n_bytes;
896
897   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
898
899   priv = self->priv;
900
901   n_chars = clutter_text_buffer_get_length (get_buffer (self));
902   if (priv->preedit_set)
903     n_chars += priv->preedit_n_chars;
904
905   if (position < -1 || position > n_chars)
906     return FALSE;
907
908   if (priv->password_char != 0)
909     password_char_bytes = g_unichar_to_utf8 (priv->password_char, NULL);
910
911   if (position == -1)
912     {
913       if (priv->password_char == 0)
914         {
915           n_bytes = clutter_text_buffer_get_bytes (get_buffer (self));
916           if (priv->editable && priv->preedit_set)
917             index_ = n_bytes + strlen (priv->preedit_str);
918           else
919             index_ = n_bytes;
920         }
921       else
922         index_ = n_chars * password_char_bytes;
923     }
924   else if (position == 0)
925     {
926       index_ = 0;
927     }
928   else
929     {
930       gchar *text = clutter_text_get_display_text (self);
931       GString *tmp = g_string_new (text);
932       gint cursor_index;
933
934       cursor_index = offset_to_bytes (text, priv->position);
935
936       if (priv->preedit_str != NULL)
937         g_string_insert (tmp, cursor_index, priv->preedit_str);
938
939       if (priv->password_char == 0)
940         index_ = offset_to_bytes (tmp->str, position);
941       else
942         index_ = position * password_char_bytes;
943
944       g_free (text);
945       g_string_free (tmp, TRUE);
946     }
947
948   pango_layout_get_cursor_pos (clutter_text_get_layout (self),
949                                index_,
950                                &rect, NULL);
951
952   if (x)
953     {
954       *x = (gfloat) rect.x / 1024.0f;
955
956       /* Take any offset due to scrolling into account */
957       if (priv->single_line_mode)
958         *x += priv->text_x;
959     }
960
961   if (y)
962     *y = (gfloat) rect.y / 1024.0f;
963
964   if (line_height)
965     *line_height = (gfloat) rect.height / 1024.0f;
966
967   return TRUE;
968 }
969
970 static inline void
971 clutter_text_ensure_cursor_position (ClutterText *self)
972 {
973   ClutterTextPrivate *priv = self->priv;
974   gfloat x, y, cursor_height;
975   ClutterGeometry cursor_pos = { 0, };
976   gboolean x_changed, y_changed;
977   gboolean width_changed, height_changed;
978   gint position;
979
980   position = priv->position;
981
982   if (priv->editable && priv->preedit_set)
983     {
984       if (position == -1)
985         position = clutter_text_buffer_get_length (get_buffer (self));
986       position += priv->preedit_cursor_pos;
987     }
988
989   CLUTTER_NOTE (MISC, "Cursor at %d (preedit %s at pos: %d)",
990                 position,
991                 priv->preedit_set ? "set" : "unset",
992                 priv->preedit_set ? priv->preedit_cursor_pos : 0);
993
994   x = y = cursor_height = 0;
995   clutter_text_position_to_coords (self, position,
996                                    &x, &y,
997                                    &cursor_height);
998
999   cursor_pos.x      = x;
1000   cursor_pos.y      = y + 2;
1001   cursor_pos.width  = priv->cursor_size;
1002   cursor_pos.height = cursor_height - 4;
1003
1004   x_changed      = priv->cursor_pos.x != cursor_pos.x;
1005   y_changed      = priv->cursor_pos.y != cursor_pos.y;
1006   width_changed  = priv->cursor_pos.width != cursor_pos.width;
1007   height_changed = priv->cursor_pos.height != cursor_pos.height;
1008
1009   if (x_changed || y_changed || width_changed || height_changed)
1010     {
1011       priv->cursor_pos = cursor_pos;
1012
1013       g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &priv->cursor_pos);
1014     }
1015 }
1016
1017 /**
1018  * clutter_text_delete_selection:
1019  * @self: a #ClutterText
1020  *
1021  * Deletes the currently selected text
1022  *
1023  * This function is only useful in subclasses of #ClutterText
1024  *
1025  * Return value: %TRUE if text was deleted or if the text actor
1026  *   is empty, and %FALSE otherwise
1027  *
1028  * Since: 1.0
1029  */
1030 gboolean
1031 clutter_text_delete_selection (ClutterText *self)
1032 {
1033   ClutterTextPrivate *priv;
1034   gint start_index;
1035   gint end_index;
1036   gint old_position, old_selection;
1037   guint n_chars;
1038
1039   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
1040
1041   priv = self->priv;
1042
1043   n_chars = clutter_text_buffer_get_length (get_buffer (self));
1044   if (n_chars == 0)
1045     return TRUE;
1046
1047   start_index = priv->position == -1 ? n_chars : priv->position;
1048   end_index = priv->selection_bound == -1 ? n_chars : priv->selection_bound;
1049
1050   if (end_index == start_index)
1051     return FALSE;
1052
1053   if (end_index < start_index)
1054     {
1055       gint temp = start_index;
1056       start_index = end_index;
1057       end_index = temp;
1058     }
1059
1060   old_position = priv->position;
1061   old_selection = priv->selection_bound;
1062
1063   clutter_text_delete_text (self, start_index, end_index);
1064
1065   priv->position = start_index;
1066   priv->selection_bound = start_index;
1067
1068   /* Not required to be guarded by g_object_freeze/thaw_notify */
1069   if (priv->position != old_position)
1070     g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]);
1071
1072   if (priv->selection_bound != old_selection)
1073     g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
1074
1075   return TRUE;
1076 }
1077
1078 /*
1079  * Utility function to update both cursor position and selection bound
1080  * at once
1081  */
1082 static inline void
1083 clutter_text_set_positions (ClutterText *self,
1084                             gint         new_pos,
1085                             gint         new_bound)
1086 {
1087   g_object_freeze_notify (G_OBJECT (self));
1088   clutter_text_set_cursor_position (self, new_pos);
1089   clutter_text_set_selection_bound (self, new_bound);
1090   g_object_thaw_notify (G_OBJECT (self));
1091 }
1092
1093 static inline void
1094 clutter_text_set_markup_internal (ClutterText *self,
1095                                   const gchar *str)
1096 {
1097   ClutterTextPrivate *priv = self->priv;
1098   GError *error;
1099   gchar *text = NULL;
1100   PangoAttrList *attrs = NULL;
1101   gboolean res;
1102
1103   g_assert (str != NULL);
1104
1105   error = NULL;
1106   res = pango_parse_markup (str, -1, 0,
1107                             &attrs,
1108                             &text,
1109                             NULL,
1110                             &error);
1111
1112   if (!res)
1113     {
1114       if (G_LIKELY (error != NULL))
1115         {
1116           g_warning ("Failed to set the markup of the actor '%s': %s",
1117                      _clutter_actor_get_debug_name (CLUTTER_ACTOR (self)),
1118                      error->message);
1119           g_error_free (error);
1120         }
1121       else
1122         g_warning ("Failed to set the markup of the actor '%s'",
1123                    _clutter_actor_get_debug_name (CLUTTER_ACTOR (self)));
1124
1125       return;
1126     }
1127
1128   if (text)
1129     {
1130       clutter_text_buffer_set_text (get_buffer (self), text, -1);
1131       g_free (text);
1132     }
1133
1134   /* Store the new markup attributes */
1135   if (priv->markup_attrs != NULL)
1136     pango_attr_list_unref (priv->markup_attrs);
1137
1138   priv->markup_attrs = attrs;
1139
1140   /* Clear the effective attributes so they will be regenerated when a
1141      layout is created */
1142   if (priv->effective_attrs != NULL)
1143     {
1144       pango_attr_list_unref (priv->effective_attrs);
1145       priv->effective_attrs = NULL;
1146     }
1147 }
1148
1149 static void
1150 clutter_text_set_property (GObject      *gobject,
1151                            guint         prop_id,
1152                            const GValue *value,
1153                            GParamSpec   *pspec)
1154 {
1155   ClutterText *self = CLUTTER_TEXT (gobject);
1156
1157   switch (prop_id)
1158     {
1159     case PROP_BUFFER:
1160       clutter_text_set_buffer (self, g_value_get_object (value));
1161       break;
1162
1163     case PROP_TEXT:
1164       {
1165         const char *str = g_value_get_string (value);
1166         if (self->priv->use_markup)
1167           clutter_text_set_markup_internal (self, str ? str : "");
1168         else
1169           clutter_text_buffer_set_text (get_buffer (self), str ? str : "", -1);
1170       }
1171       break;
1172
1173     case PROP_COLOR:
1174       clutter_text_set_color (self, clutter_value_get_color (value));
1175       break;
1176
1177     case PROP_FONT_NAME:
1178       clutter_text_set_font_name (self, g_value_get_string (value));
1179       break;
1180
1181     case PROP_FONT_DESCRIPTION:
1182       clutter_text_set_font_description (self, g_value_get_boxed (value));
1183       break;
1184
1185     case PROP_USE_MARKUP:
1186       clutter_text_set_use_markup (self, g_value_get_boolean (value));
1187       break;
1188
1189     case PROP_ATTRIBUTES:
1190       clutter_text_set_attributes (self, g_value_get_boxed (value));
1191       break;
1192
1193     case PROP_LINE_ALIGNMENT:
1194       clutter_text_set_line_alignment (self, g_value_get_enum (value));
1195       break;
1196
1197     case PROP_LINE_WRAP:
1198       clutter_text_set_line_wrap (self, g_value_get_boolean (value));
1199       break;
1200
1201     case PROP_LINE_WRAP_MODE:
1202       clutter_text_set_line_wrap_mode (self, g_value_get_enum (value));
1203       break;
1204
1205     case PROP_JUSTIFY:
1206       clutter_text_set_justify (self, g_value_get_boolean (value));
1207       break;
1208
1209     case PROP_ELLIPSIZE:
1210       clutter_text_set_ellipsize (self, g_value_get_enum (value));
1211       break;
1212
1213     case PROP_POSITION:
1214       clutter_text_set_cursor_position (self, g_value_get_int (value));
1215       break;
1216
1217     case PROP_SELECTION_BOUND:
1218       clutter_text_set_selection_bound (self, g_value_get_int (value));
1219       break;
1220
1221     case PROP_SELECTION_COLOR:
1222       clutter_text_set_selection_color (self, g_value_get_boxed (value));
1223       break;
1224
1225     case PROP_CURSOR_VISIBLE:
1226       clutter_text_set_cursor_visible (self, g_value_get_boolean (value));
1227       break;
1228
1229     case PROP_CURSOR_COLOR:
1230       clutter_text_set_cursor_color (self, g_value_get_boxed (value));
1231       break;
1232
1233     case PROP_CURSOR_SIZE:
1234       clutter_text_set_cursor_size (self, g_value_get_int (value));
1235       break;
1236
1237     case PROP_EDITABLE:
1238       clutter_text_set_editable (self, g_value_get_boolean (value));
1239       break;
1240
1241     case PROP_ACTIVATABLE:
1242       clutter_text_set_activatable (self, g_value_get_boolean (value));
1243       break;
1244
1245     case PROP_SELECTABLE:
1246       clutter_text_set_selectable (self, g_value_get_boolean (value));
1247       break;
1248
1249     case PROP_PASSWORD_CHAR:
1250       clutter_text_set_password_char (self, g_value_get_uint (value));
1251       break;
1252
1253     case PROP_MAX_LENGTH:
1254       clutter_text_set_max_length (self, g_value_get_int (value));
1255       break;
1256
1257     case PROP_SINGLE_LINE_MODE:
1258       clutter_text_set_single_line_mode (self, g_value_get_boolean (value));
1259       break;
1260
1261     case PROP_SELECTED_TEXT_COLOR:
1262       clutter_text_set_selected_text_color (self, clutter_value_get_color (value));
1263       break;
1264
1265     default:
1266       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1267     }
1268 }
1269
1270 static void
1271 clutter_text_get_property (GObject    *gobject,
1272                            guint       prop_id,
1273                            GValue     *value,
1274                            GParamSpec *pspec)
1275 {
1276   ClutterText *self = CLUTTER_TEXT (gobject);
1277   ClutterTextPrivate *priv = self->priv;
1278
1279   switch (prop_id)
1280     {
1281     case PROP_BUFFER:
1282       g_value_set_object (value, clutter_text_get_buffer (self));
1283       break;
1284
1285     case PROP_TEXT:
1286       g_value_set_string (value, clutter_text_buffer_get_text (get_buffer (self)));
1287       break;
1288
1289     case PROP_FONT_NAME:
1290       g_value_set_string (value, priv->font_name);
1291       break;
1292
1293     case PROP_FONT_DESCRIPTION:
1294       g_value_set_boxed (value, priv->font_desc);
1295       break;
1296
1297     case PROP_USE_MARKUP:
1298       g_value_set_boolean (value, priv->use_markup);
1299       break;
1300
1301     case PROP_COLOR:
1302       clutter_value_set_color (value, &priv->text_color);
1303       break;
1304
1305     case PROP_CURSOR_VISIBLE:
1306       g_value_set_boolean (value, priv->cursor_visible);
1307       break;
1308
1309     case PROP_CURSOR_COLOR:
1310       clutter_value_set_color (value, &priv->cursor_color);
1311       break;
1312
1313     case PROP_CURSOR_COLOR_SET:
1314       g_value_set_boolean (value, priv->cursor_color_set);
1315       break;
1316
1317     case PROP_CURSOR_SIZE:
1318       g_value_set_int (value, priv->cursor_size);
1319       break;
1320
1321     case PROP_POSITION:
1322       g_value_set_int (value, priv->position);
1323       break;
1324
1325     case PROP_SELECTION_BOUND:
1326       g_value_set_int (value, priv->selection_bound);
1327       break;
1328
1329     case PROP_EDITABLE:
1330       g_value_set_boolean (value, priv->editable);
1331       break;
1332
1333     case PROP_SELECTABLE:
1334       g_value_set_boolean (value, priv->selectable);
1335       break;
1336
1337     case PROP_SELECTION_COLOR:
1338       clutter_value_set_color (value, &priv->selection_color);
1339       break;
1340
1341     case PROP_SELECTION_COLOR_SET:
1342       g_value_set_boolean (value, priv->selection_color_set);
1343       break;
1344
1345     case PROP_ACTIVATABLE:
1346       g_value_set_boolean (value, priv->activatable);
1347       break;
1348
1349     case PROP_PASSWORD_CHAR:
1350       g_value_set_uint (value, priv->password_char);
1351       break;
1352
1353     case PROP_MAX_LENGTH:
1354       g_value_set_int (value, clutter_text_buffer_get_max_length (get_buffer (self)));
1355       break;
1356
1357     case PROP_SINGLE_LINE_MODE:
1358       g_value_set_boolean (value, priv->single_line_mode);
1359       break;
1360
1361     case PROP_ELLIPSIZE:
1362       g_value_set_enum (value, priv->ellipsize);
1363       break;
1364
1365     case PROP_LINE_WRAP:
1366       g_value_set_boolean (value, priv->wrap);
1367       break;
1368
1369     case PROP_LINE_WRAP_MODE:
1370       g_value_set_enum (value, priv->wrap_mode);
1371       break;
1372
1373     case PROP_LINE_ALIGNMENT:
1374       g_value_set_enum (value, priv->alignment);
1375       break;
1376
1377     case PROP_JUSTIFY:
1378       g_value_set_boolean (value, priv->justify);
1379       break;
1380
1381     case PROP_ATTRIBUTES:
1382       g_value_set_boxed (value, priv->attrs);
1383       break;
1384
1385     case PROP_SELECTED_TEXT_COLOR:
1386       clutter_value_set_color (value, &priv->selected_text_color);
1387       break;
1388
1389     case PROP_SELECTED_TEXT_COLOR_SET:
1390       g_value_set_boolean (value, priv->selected_text_color_set);
1391       break;
1392
1393     default:
1394       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1395     }
1396 }
1397
1398 static void
1399 clutter_text_dispose (GObject *gobject)
1400 {
1401   ClutterText *self = CLUTTER_TEXT (gobject);
1402   ClutterTextPrivate *priv = self->priv;
1403
1404   /* get rid of the entire cache */
1405   clutter_text_dirty_cache (self);
1406
1407   if (priv->direction_changed_id)
1408     {
1409       g_signal_handler_disconnect (self, priv->direction_changed_id);
1410       priv->direction_changed_id = 0;
1411     }
1412
1413   if (priv->settings_changed_id)
1414     {
1415       g_signal_handler_disconnect (clutter_get_default_backend (),
1416                                    priv->settings_changed_id);
1417       priv->settings_changed_id = 0;
1418     }
1419
1420   if (priv->password_hint_id)
1421     {
1422       g_source_remove (priv->password_hint_id);
1423       priv->password_hint_id = 0;
1424     }
1425
1426   clutter_text_set_buffer (self, NULL);
1427
1428   G_OBJECT_CLASS (clutter_text_parent_class)->dispose (gobject);
1429 }
1430
1431 static void
1432 clutter_text_finalize (GObject *gobject)
1433 {
1434   ClutterText *self = CLUTTER_TEXT (gobject);
1435   ClutterTextPrivate *priv = self->priv;
1436
1437   if (priv->font_desc)
1438     pango_font_description_free (priv->font_desc);
1439
1440   if (priv->attrs)
1441     pango_attr_list_unref (priv->attrs);
1442   if (priv->markup_attrs)
1443     pango_attr_list_unref (priv->markup_attrs);
1444   if (priv->effective_attrs)
1445     pango_attr_list_unref (priv->effective_attrs);
1446   if (priv->preedit_attrs)
1447     pango_attr_list_unref (priv->preedit_attrs);
1448
1449   clutter_text_dirty_paint_volume (self);
1450
1451   clutter_text_set_buffer (self, NULL);
1452   g_free (priv->font_name);
1453
1454   G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject);
1455 }
1456
1457 typedef void (* ClutterTextSelectionFunc) (ClutterText           *text,
1458                                            const ClutterActorBox *box,
1459                                            gpointer               user_data);
1460
1461 static void
1462 clutter_text_foreach_selection_rectangle (ClutterText              *self,
1463                                           ClutterTextSelectionFunc  func,
1464                                           gpointer                  user_data)
1465 {
1466   ClutterTextPrivate *priv = self->priv;
1467   PangoLayout *layout = clutter_text_get_layout (self);
1468   gchar *utf8 = clutter_text_get_display_text (self);
1469   gint lines;
1470   gint start_index;
1471   gint end_index;
1472   gint line_no;
1473
1474   if (priv->position == 0)
1475     start_index = 0;
1476   else
1477     start_index = offset_to_bytes (utf8, priv->position);
1478
1479   if (priv->selection_bound == 0)
1480     end_index = 0;
1481   else
1482     end_index = offset_to_bytes (utf8, priv->selection_bound);
1483
1484   if (start_index > end_index)
1485     {
1486       gint temp = start_index;
1487       start_index = end_index;
1488       end_index = temp;
1489     }
1490
1491   lines = pango_layout_get_line_count (layout);
1492
1493   for (line_no = 0; line_no < lines; line_no++)
1494     {
1495       PangoLayoutLine *line;
1496       gint n_ranges;
1497       gint *ranges;
1498       gint i;
1499       gint index_;
1500       gint maxindex;
1501       ClutterActorBox box;
1502       gfloat y, height;
1503
1504       line = pango_layout_get_line_readonly (layout, line_no);
1505       pango_layout_line_x_to_index (line, G_MAXINT, &maxindex, NULL);
1506       if (maxindex < start_index)
1507         continue;
1508
1509       pango_layout_line_get_x_ranges (line, start_index, end_index,
1510                                       &ranges,
1511                                       &n_ranges);
1512       pango_layout_line_x_to_index (line, 0, &index_, NULL);
1513
1514       clutter_text_position_to_coords (self,
1515                                        bytes_to_offset (utf8, index_),
1516                                        NULL, &y, &height);
1517
1518       box.y1 = y;
1519       box.y2 = y + height;
1520
1521       for (i = 0; i < n_ranges; i++)
1522         {
1523           gfloat range_x;
1524           gfloat range_width;
1525
1526           range_x = ranges[i * 2] / PANGO_SCALE;
1527
1528           /* Account for any scrolling in single line mode */
1529           if (priv->single_line_mode)
1530             range_x += priv->text_x;
1531
1532
1533           range_width = ((gfloat) ranges[i * 2 + 1] - (gfloat) ranges[i * 2])
1534                       / PANGO_SCALE;
1535
1536           box.x1 = range_x;
1537           box.x2 = ceilf (range_x + range_width + .5f);
1538
1539           func (self, &box, user_data);
1540         }
1541
1542       g_free (ranges);
1543     }
1544
1545   g_free (utf8);
1546 }
1547
1548 static void
1549 add_selection_rectangle_to_path (ClutterText           *text,
1550                                  const ClutterActorBox *box,
1551                                  gpointer               user_data)
1552 {
1553   cogl_path_rectangle (user_data, box->x1, box->y1, box->x2, box->y2);
1554 }
1555
1556 /* Draws the selected text, its background, and the cursor */
1557 static void
1558 selection_paint (ClutterText *self)
1559 {
1560   ClutterTextPrivate *priv = self->priv;
1561   ClutterActor *actor = CLUTTER_ACTOR (self);
1562   guint8 paint_opacity = clutter_actor_get_paint_opacity (actor);
1563
1564   if (!priv->has_focus)
1565     return;
1566
1567   if (priv->editable && priv->cursor_visible)
1568     {
1569       const ClutterColor *color;
1570       gint position;
1571
1572       position = priv->position;
1573
1574       if (position == priv->selection_bound)
1575         {
1576           /* No selection, just draw the cursor */
1577           if (priv->cursor_color_set)
1578             color = &priv->cursor_color;
1579           else
1580             color = &priv->text_color;
1581
1582           cogl_set_source_color4ub (color->red,
1583                                     color->green,
1584                                     color->blue,
1585                                     paint_opacity
1586                                     * color->alpha
1587                                     / 255);
1588
1589           cogl_rectangle (priv->cursor_pos.x,
1590                           priv->cursor_pos.y,
1591                           priv->cursor_pos.x + priv->cursor_pos.width,
1592                           priv->cursor_pos.y + priv->cursor_pos.height);
1593         }
1594       else
1595         {
1596           /* Paint selection background first */
1597           PangoLayout *layout = clutter_text_get_layout (self);
1598           CoglPath *selection_path = cogl_path_new ();
1599           CoglColor cogl_color = { 0, };
1600
1601           /* Paint selection background */
1602           if (priv->selection_color_set)
1603             color = &priv->selection_color;
1604           else if (priv->cursor_color_set)
1605             color = &priv->cursor_color;
1606           else
1607             color = &priv->text_color;
1608
1609           cogl_set_source_color4ub (color->red,
1610                                     color->green,
1611                                     color->blue,
1612                                     paint_opacity * color->alpha / 255);
1613
1614           clutter_text_foreach_selection_rectangle (self,
1615                                                     add_selection_rectangle_to_path,
1616                                                     selection_path);
1617
1618           cogl_path_fill (selection_path);
1619
1620           /* Paint selected text */
1621           cogl_framebuffer_push_path_clip (cogl_get_draw_framebuffer (),
1622                                            selection_path);
1623           cogl_object_unref (selection_path);
1624
1625           if (priv->selected_text_color_set)
1626             color = &priv->selected_text_color;
1627           else
1628             color = &priv->text_color;
1629
1630           cogl_color_init_from_4ub (&cogl_color,
1631                                     color->red,
1632                                     color->green,
1633                                     color->blue,
1634                                     paint_opacity * color->alpha / 255);
1635
1636           cogl_pango_render_layout (layout, priv->text_x, 0, &cogl_color, 0);
1637
1638           cogl_clip_pop ();
1639         }
1640     }
1641 }
1642
1643 static gint
1644 clutter_text_move_word_backward (ClutterText *self,
1645                                  gint         start)
1646 {
1647   gint retval = start;
1648
1649   if (clutter_text_buffer_get_length (get_buffer (self)) > 0 && start > 0)
1650     {
1651       PangoLayout *layout = clutter_text_get_layout (self);
1652       PangoLogAttr *log_attrs = NULL;
1653       gint n_attrs = 0;
1654
1655       pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
1656
1657       retval = start - 1;
1658       while (retval > 0 && !log_attrs[retval].is_word_start)
1659         retval -= 1;
1660
1661       g_free (log_attrs);
1662     }
1663
1664   return retval;
1665 }
1666
1667 static gint
1668 clutter_text_move_word_forward (ClutterText *self,
1669                                 gint         start)
1670 {
1671   gint retval = start;
1672   guint n_chars;
1673
1674   n_chars = clutter_text_buffer_get_length (get_buffer (self));
1675   if (n_chars > 0 && start < n_chars)
1676     {
1677       PangoLayout *layout = clutter_text_get_layout (self);
1678       PangoLogAttr *log_attrs = NULL;
1679       gint n_attrs = 0;
1680
1681       pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
1682
1683       retval = start + 1;
1684       while (retval < n_chars && !log_attrs[retval].is_word_end)
1685         retval += 1;
1686
1687       g_free (log_attrs);
1688     }
1689
1690   return retval;
1691 }
1692
1693 static gint
1694 clutter_text_move_line_start (ClutterText *self,
1695                               gint         start)
1696 {
1697   PangoLayoutLine *layout_line;
1698   PangoLayout *layout;
1699   gint line_no;
1700   gint index_;
1701   gint position;
1702   const gchar *text;
1703
1704   layout = clutter_text_get_layout (self);
1705   text = clutter_text_buffer_get_text (get_buffer (self));
1706
1707   if (start == 0)
1708     index_ = 0;
1709   else
1710     index_ = offset_to_bytes (text, start);
1711
1712   pango_layout_index_to_line_x (layout, index_,
1713                                 0,
1714                                 &line_no, NULL);
1715
1716   layout_line = pango_layout_get_line_readonly (layout, line_no);
1717   if (!layout_line)
1718     return FALSE;
1719
1720   pango_layout_line_x_to_index (layout_line, 0, &index_, NULL);
1721
1722   position = bytes_to_offset (text, index_);
1723
1724   return position;
1725 }
1726
1727 static gint
1728 clutter_text_move_line_end (ClutterText *self,
1729                             gint         start)
1730 {
1731   ClutterTextPrivate *priv = self->priv;
1732   PangoLayoutLine *layout_line;
1733   PangoLayout *layout;
1734   gint line_no;
1735   gint index_;
1736   gint trailing;
1737   gint position;
1738   const gchar *text;
1739
1740   layout = clutter_text_get_layout (self);
1741   text = clutter_text_buffer_get_text (get_buffer (self));
1742
1743   if (start == 0)
1744     index_ = 0;
1745   else
1746     index_ = offset_to_bytes (text, priv->position);
1747
1748   pango_layout_index_to_line_x (layout, index_,
1749                                 0,
1750                                 &line_no, NULL);
1751
1752   layout_line = pango_layout_get_line_readonly (layout, line_no);
1753   if (!layout_line)
1754     return FALSE;
1755
1756   pango_layout_line_x_to_index (layout_line, G_MAXINT, &index_, &trailing);
1757   index_ += trailing;
1758
1759   position = bytes_to_offset (text, index_);
1760
1761   return position;
1762 }
1763
1764 static void
1765 clutter_text_select_word (ClutterText *self)
1766 {
1767   gint cursor_pos = self->priv->position;
1768   gint start_pos, end_pos;
1769
1770   start_pos = clutter_text_move_word_backward (self, cursor_pos);
1771   end_pos   = clutter_text_move_word_forward (self, cursor_pos);
1772
1773   clutter_text_set_selection (self, start_pos, end_pos);
1774 }
1775
1776 static void
1777 clutter_text_select_line (ClutterText *self)
1778 {
1779   ClutterTextPrivate *priv = self->priv;
1780   gint cursor_pos = priv->position;
1781   gint start_pos, end_pos;
1782
1783   if (priv->single_line_mode)
1784     {
1785       start_pos = 0;
1786       end_pos   = -1;
1787     }
1788   else
1789     {
1790       start_pos = clutter_text_move_line_start (self, cursor_pos);
1791       end_pos   = clutter_text_move_line_end (self, cursor_pos);
1792     }
1793
1794   clutter_text_set_selection (self, start_pos, end_pos);
1795 }
1796
1797 static gboolean
1798 clutter_text_button_press (ClutterActor       *actor,
1799                            ClutterButtonEvent *event)
1800 {
1801   ClutterText *self = CLUTTER_TEXT (actor);
1802   ClutterTextPrivate *priv = self->priv;
1803   gboolean res = FALSE;
1804   gfloat x, y;
1805   gint index_;
1806
1807   /* we'll steal keyfocus if we need it */
1808   if (priv->editable || priv->selectable)
1809     clutter_actor_grab_key_focus (actor);
1810   else
1811     return CLUTTER_EVENT_PROPAGATE;
1812
1813   /* if the actor is empty we just reset everything and not
1814    * set up the dragging of the selection since there's nothing
1815    * to select
1816    */
1817   if (clutter_text_buffer_get_length (get_buffer (self)) == 0)
1818     {
1819       clutter_text_set_positions (self, -1, -1);
1820
1821       return CLUTTER_EVENT_STOP;
1822     }
1823
1824   res = clutter_actor_transform_stage_point (actor,
1825                                              event->x,
1826                                              event->y,
1827                                              &x, &y);
1828   if (res)
1829     {
1830       gint offset;
1831       const char *text;
1832
1833       index_ = clutter_text_coords_to_position (self, x, y);
1834       text = clutter_text_buffer_get_text (get_buffer (self));
1835       offset = bytes_to_offset (text, index_);
1836
1837       /* what we select depends on the number of button clicks we
1838        * receive:
1839        *
1840        *   1: just position the cursor and the selection
1841        *   2: select the current word
1842        *   3: select the contents of the whole actor
1843        */
1844       if (event->click_count == 1)
1845         {
1846           clutter_text_set_positions (self, offset, offset);
1847         }
1848       else if (event->click_count == 2)
1849         {
1850           clutter_text_select_word (self);
1851         }
1852       else if (event->click_count == 3)
1853         {
1854           clutter_text_select_line (self);
1855         }
1856     }
1857
1858   /* grab the pointer */
1859   priv->in_select_drag = TRUE;
1860   clutter_grab_pointer (actor);
1861
1862   return CLUTTER_EVENT_STOP;
1863 }
1864
1865 static gboolean
1866 clutter_text_motion (ClutterActor       *actor,
1867                      ClutterMotionEvent *mev)
1868 {
1869   ClutterText *self = CLUTTER_TEXT (actor);
1870   ClutterTextPrivate *priv = self->priv;
1871   gfloat x, y;
1872   gint index_, offset;
1873   gboolean res;
1874   const gchar *text;
1875
1876   if (!priv->in_select_drag)
1877     return CLUTTER_EVENT_PROPAGATE;
1878
1879   res = clutter_actor_transform_stage_point (actor,
1880                                              mev->x, mev->y,
1881                                              &x, &y);
1882   if (!res)
1883     return CLUTTER_EVENT_PROPAGATE;
1884
1885   index_ = clutter_text_coords_to_position (self, x, y);
1886   text = clutter_text_buffer_get_text (get_buffer (self));
1887   offset = bytes_to_offset (text, index_);
1888
1889   if (priv->selectable)
1890     clutter_text_set_cursor_position (self, offset);
1891   else
1892     clutter_text_set_positions (self, offset, offset);
1893
1894   return CLUTTER_EVENT_STOP;
1895 }
1896
1897 static gboolean
1898 clutter_text_button_release (ClutterActor       *actor,
1899                              ClutterButtonEvent *bev)
1900 {
1901   ClutterText *self = CLUTTER_TEXT (actor);
1902   ClutterTextPrivate *priv = self->priv;
1903
1904   if (priv->in_select_drag)
1905     {
1906       clutter_ungrab_pointer ();
1907       priv->in_select_drag = FALSE;
1908
1909       return CLUTTER_EVENT_STOP;
1910     }
1911
1912   return CLUTTER_EVENT_PROPAGATE;
1913 }
1914
1915 static gboolean
1916 clutter_text_remove_password_hint (gpointer data)
1917 {
1918   ClutterText *self = data;
1919
1920   self->priv->password_hint_visible = FALSE;
1921   self->priv->password_hint_id = 0;
1922
1923   clutter_text_dirty_cache (data);
1924   clutter_text_queue_redraw (data);
1925
1926   return G_SOURCE_REMOVE;
1927 }
1928
1929 static gboolean
1930 clutter_text_key_press (ClutterActor    *actor,
1931                         ClutterKeyEvent *event)
1932 {
1933   ClutterText *self = CLUTTER_TEXT (actor);
1934   ClutterTextPrivate *priv = self->priv;
1935   ClutterBindingPool *pool;
1936   gboolean res;
1937
1938   if (!priv->editable)
1939     return CLUTTER_EVENT_PROPAGATE;
1940
1941   /* we need to use the ClutterText type name to find our own
1942    * key bindings; subclasses will override or chain up this
1943    * event handler, so they can do whatever they want there
1944    */
1945   pool = clutter_binding_pool_find (g_type_name (CLUTTER_TYPE_TEXT));
1946   g_assert (pool != NULL);
1947
1948   /* we allow passing synthetic events that only contain
1949    * the Unicode value and not the key symbol
1950    */
1951   if (event->keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC))
1952     res = FALSE;
1953   else
1954     res = clutter_binding_pool_activate (pool, event->keyval,
1955                                          event->modifier_state,
1956                                          G_OBJECT (actor));
1957
1958   /* if the key binding has handled the event we bail out
1959    * as fast as we can; otherwise, we try to insert the
1960    * Unicode character inside the key event into the text
1961    * actor
1962    */
1963   if (res)
1964     return CLUTTER_EVENT_STOP;
1965   else if ((event->modifier_state & CLUTTER_CONTROL_MASK) == 0)
1966     {
1967       gunichar key_unichar;
1968
1969       /* Skip keys when control is pressed */
1970       key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) event);
1971
1972       /* return is reported as CR, but we want LF */
1973       if (key_unichar == '\r')
1974         key_unichar = '\n';
1975
1976       if (key_unichar == '\n' ||
1977           (g_unichar_validate (key_unichar) &&
1978            !g_unichar_iscntrl (key_unichar)))
1979         {
1980           /* truncate the eventual selection so that the
1981            * Unicode character can replace it
1982            */
1983           clutter_text_delete_selection (self);
1984           clutter_text_insert_unichar (self, key_unichar);
1985
1986           if (priv->show_password_hint)
1987             {
1988               if (priv->password_hint_id != 0)
1989                 g_source_remove (priv->password_hint_id);
1990
1991               priv->password_hint_visible = TRUE;
1992               priv->password_hint_id =
1993                 clutter_threads_add_timeout (priv->password_hint_timeout,
1994                                              clutter_text_remove_password_hint,
1995                                              self);
1996             }
1997
1998           return CLUTTER_EVENT_STOP;
1999         }
2000     }
2001
2002   return CLUTTER_EVENT_PROPAGATE;
2003 }
2004
2005 #define TEXT_PADDING    2
2006
2007 static void
2008 clutter_text_paint (ClutterActor *self)
2009 {
2010   ClutterText *text = CLUTTER_TEXT (self);
2011   ClutterTextPrivate *priv = text->priv;
2012   PangoLayout *layout;
2013   ClutterActorBox alloc = { 0, };
2014   CoglColor color = { 0, };
2015   guint8 real_opacity;
2016   gint text_x = priv->text_x;
2017   gboolean clip_set = FALSE;
2018   gboolean bg_color_set = FALSE;
2019   guint n_chars;
2020
2021   /* Note that if anything in this paint method changes it needs to be
2022      reflected in the get_paint_volume implementation which is tightly
2023      tied to the workings of this function */
2024
2025   n_chars = clutter_text_buffer_get_length (get_buffer (text));
2026
2027   /* don't bother painting an empty text actor, unless it's
2028    * editable, in which case we want to paint at least the
2029    * cursor
2030    */
2031   if (n_chars == 0 && (!priv->editable || !priv->cursor_visible))
2032     return;
2033
2034   clutter_actor_get_allocation_box (self, &alloc);
2035
2036   g_object_get (self, "background-color-set", &bg_color_set, NULL);
2037   if (bg_color_set)
2038     {
2039       ClutterColor bg_color;
2040
2041       clutter_actor_get_background_color (self, &bg_color);
2042       bg_color.alpha = clutter_actor_get_paint_opacity (self)
2043                      * bg_color.alpha
2044                      / 255;
2045
2046       cogl_set_source_color4ub (bg_color.red,
2047                                 bg_color.green,
2048                                 bg_color.blue,
2049                                 bg_color.alpha);
2050       cogl_rectangle (0, 0, alloc.x2 - alloc.x1, alloc.y2 - alloc.y1);
2051     }
2052
2053   if (priv->editable && priv->single_line_mode)
2054     layout = clutter_text_create_layout (text, -1, -1);
2055   else
2056     {
2057       /* the only time when we create the PangoLayout using the full
2058        * width and height of the allocation is when we can both wrap
2059        * and ellipsize
2060        */
2061       if (priv->wrap && priv->ellipsize)
2062         {
2063           layout = clutter_text_create_layout (text,
2064                                                alloc.x2 - alloc.x1,
2065                                                alloc.y2 - alloc.y1);
2066         }
2067       else
2068         {
2069           /* if we're not wrapping we cannot set the height of the
2070            * layout, otherwise Pango will happily wrap the text to
2071            * fit in the rectangle - thus making the :wrap property
2072            * useless
2073            *
2074            * see bug:
2075            *
2076            *   http://bugzilla.clutter-project.org/show_bug.cgi?id=2339
2077            *
2078            * in order to fix this, we create a layout that would fit
2079            * in the assigned width, then we clip the actor if the
2080            * logical rectangle overflows the allocation.
2081            */
2082           layout = clutter_text_create_layout (text,
2083                                                alloc.x2 - alloc.x1,
2084                                                -1);
2085         }
2086     }
2087
2088   if (priv->editable && priv->cursor_visible)
2089     clutter_text_ensure_cursor_position (text);
2090
2091   if (priv->editable && priv->single_line_mode)
2092     {
2093       PangoRectangle logical_rect = { 0, };
2094       gint actor_width, text_width;
2095
2096       pango_layout_get_extents (layout, NULL, &logical_rect);
2097
2098       cogl_clip_push_rectangle (0, 0,
2099                                 (alloc.x2 - alloc.x1),
2100                                 (alloc.y2 - alloc.y1));
2101       clip_set = TRUE;
2102
2103       actor_width = (alloc.x2 - alloc.x1)
2104                   - 2 * TEXT_PADDING;
2105       text_width  = logical_rect.width / PANGO_SCALE;
2106
2107       if (actor_width < text_width)
2108         {
2109           gint cursor_x = priv->cursor_pos.x;
2110
2111           if (priv->position == -1)
2112             {
2113               text_x = actor_width - text_width;
2114             }
2115           else if (priv->position == 0)
2116             {
2117               text_x = TEXT_PADDING;
2118             }
2119           else
2120             {
2121               if (cursor_x < 0)
2122                 {
2123                   text_x = text_x - cursor_x - TEXT_PADDING;
2124                 }
2125               else if (cursor_x > actor_width)
2126                 {
2127                   text_x = text_x + (actor_width - cursor_x) - TEXT_PADDING;
2128                 }
2129             }
2130         }
2131       else
2132         {
2133           text_x = TEXT_PADDING;
2134         }
2135     }
2136   else if (!priv->editable && !(priv->wrap && priv->ellipsize))
2137     {
2138       PangoRectangle logical_rect = { 0, };
2139
2140       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2141
2142       /* don't clip if the layout managed to fit inside our allocation */
2143       if (logical_rect.width > (alloc.x2 - alloc.x1) ||
2144           logical_rect.height > (alloc.y2 - alloc.y1))
2145         {
2146           cogl_clip_push_rectangle (0, 0,
2147                                     alloc.x2 - alloc.x1,
2148                                     alloc.y2 - alloc.y1);
2149           clip_set = TRUE;
2150         }
2151
2152       text_x = 0;
2153     }
2154   else
2155     text_x = 0;
2156
2157   if (priv->text_x != text_x)
2158     {
2159       priv->text_x = text_x;
2160       clutter_text_ensure_cursor_position (text);
2161     }
2162
2163   real_opacity = clutter_actor_get_paint_opacity (self)
2164                * priv->text_color.alpha
2165                / 255;
2166
2167   CLUTTER_NOTE (PAINT, "painting text (text: '%s')",
2168                 clutter_text_buffer_get_text (get_buffer (text)));
2169
2170   cogl_color_init_from_4ub (&color,
2171                             priv->text_color.red,
2172                             priv->text_color.green,
2173                             priv->text_color.blue,
2174                             real_opacity);
2175   cogl_pango_render_layout (layout, text_x, priv->text_y, &color, 0);
2176
2177   selection_paint (text);
2178
2179   if (clip_set)
2180     cogl_clip_pop ();
2181 }
2182
2183 static void
2184 add_selection_to_paint_volume (ClutterText           *text,
2185                                const ClutterActorBox *box,
2186                                gpointer               user_data)
2187 {
2188   ClutterPaintVolume *total_volume = user_data;
2189   ClutterPaintVolume rect_volume;
2190   ClutterVertex vertex;
2191
2192   _clutter_paint_volume_init_static (&rect_volume, CLUTTER_ACTOR (text));
2193
2194   vertex.x = box->x1;
2195   vertex.y = box->y1;
2196   vertex.z = 0.0f;
2197   clutter_paint_volume_set_origin (&rect_volume, &vertex);
2198   clutter_paint_volume_set_width (&rect_volume, box->x2 - box->x1);
2199   clutter_paint_volume_set_height (&rect_volume, box->y2 - box->y1);
2200
2201   clutter_paint_volume_union (total_volume, &rect_volume);
2202
2203   clutter_paint_volume_free (&rect_volume);
2204 }
2205
2206 static void
2207 clutter_text_get_paint_volume_for_cursor (ClutterText        *text,
2208                                           ClutterPaintVolume *volume)
2209 {
2210   ClutterTextPrivate *priv = text->priv;
2211   ClutterVertex origin;
2212
2213   clutter_text_ensure_cursor_position (text);
2214
2215   if (priv->position == priv->selection_bound)
2216     {
2217       origin.x = priv->cursor_pos.x;
2218       origin.y = priv->cursor_pos.y;
2219       origin.z = 0;
2220       clutter_paint_volume_set_origin (volume, &origin);
2221       clutter_paint_volume_set_width (volume, priv->cursor_pos.width);
2222       clutter_paint_volume_set_height (volume, priv->cursor_pos.height);
2223     }
2224   else
2225     {
2226       clutter_text_foreach_selection_rectangle (text,
2227                                                 add_selection_to_paint_volume,
2228                                                 volume);
2229     }
2230 }
2231
2232 static gboolean
2233 clutter_text_get_paint_volume (ClutterActor       *self,
2234                                ClutterPaintVolume *volume)
2235 {
2236   ClutterText *text = CLUTTER_TEXT (self);
2237   ClutterTextPrivate *priv = text->priv;
2238
2239   /* ClutterText uses the logical layout as the natural size of the
2240      actor. This means that it can sometimes paint outside of its
2241      allocation for example with italic fonts with serifs. Therefore
2242      we should use the ink rectangle of the layout instead */
2243
2244   if (!priv->paint_volume_valid)
2245     {
2246       PangoLayout *layout;
2247       PangoRectangle ink_rect;
2248       ClutterVertex origin;
2249
2250       /* If the text is single line editable then it gets clipped to
2251          the allocation anyway so we can just use that */
2252       if (priv->editable && priv->single_line_mode)
2253         return _clutter_actor_set_default_paint_volume (self,
2254                                                         CLUTTER_TYPE_TEXT,
2255                                                         volume);
2256
2257       if (G_OBJECT_TYPE (self) != CLUTTER_TYPE_TEXT)
2258         return FALSE;
2259
2260       if (!clutter_actor_has_allocation (self))
2261         return FALSE;
2262
2263       _clutter_paint_volume_init_static (&priv->paint_volume, self);
2264
2265       layout = clutter_text_get_layout (text);
2266       pango_layout_get_extents (layout, &ink_rect, NULL);
2267
2268       origin.x = ink_rect.x / (float) PANGO_SCALE;
2269       origin.y = ink_rect.y / (float) PANGO_SCALE;
2270       origin.z = 0;
2271       clutter_paint_volume_set_origin (&priv->paint_volume, &origin);
2272       clutter_paint_volume_set_width (&priv->paint_volume,
2273                                       ink_rect.width / (float) PANGO_SCALE);
2274       clutter_paint_volume_set_height (&priv->paint_volume,
2275                                        ink_rect.height / (float) PANGO_SCALE);
2276
2277       /* If the cursor is visible then that will likely be drawn
2278          outside of the ink rectangle so we should merge that in */
2279       if (priv->editable && priv->cursor_visible && priv->has_focus)
2280         {
2281           ClutterPaintVolume cursor_paint_volume;
2282
2283           _clutter_paint_volume_init_static (&cursor_paint_volume,
2284                                              self);
2285
2286           clutter_text_get_paint_volume_for_cursor (text, &cursor_paint_volume);
2287
2288           clutter_paint_volume_union (&priv->paint_volume,
2289                                       &cursor_paint_volume);
2290
2291           clutter_paint_volume_free (&cursor_paint_volume);
2292         }
2293
2294       priv->paint_volume_valid = TRUE;
2295     }
2296
2297   _clutter_paint_volume_copy_static (&priv->paint_volume, volume);
2298
2299   return TRUE;
2300 }
2301
2302 static void
2303 clutter_text_get_preferred_width (ClutterActor *self,
2304                                   gfloat        for_height,
2305                                   gfloat       *min_width_p,
2306                                   gfloat       *natural_width_p)
2307 {
2308   ClutterText *text = CLUTTER_TEXT (self);
2309   ClutterTextPrivate *priv = text->priv;
2310   PangoRectangle logical_rect = { 0, };
2311   PangoLayout *layout;
2312   gint logical_width;
2313   gfloat layout_width;
2314
2315   layout = clutter_text_create_layout (text, -1, -1);
2316
2317   pango_layout_get_extents (layout, NULL, &logical_rect);
2318
2319   /* the X coordinate of the logical rectangle might be non-zero
2320    * according to the Pango documentation; hence, we need to offset
2321    * the width accordingly
2322    */
2323   logical_width = logical_rect.x + logical_rect.width;
2324
2325   layout_width = logical_width > 0
2326     ? ceilf (logical_width / 1024.0f)
2327     : 1;
2328
2329   if (min_width_p)
2330     {
2331       if (priv->wrap || priv->ellipsize || priv->editable)
2332         *min_width_p = 1;
2333       else
2334         *min_width_p = layout_width;
2335     }
2336
2337   if (natural_width_p)
2338     {
2339       if (priv->editable && priv->single_line_mode)
2340         *natural_width_p = layout_width + TEXT_PADDING * 2;
2341       else
2342         *natural_width_p = layout_width;
2343     }
2344 }
2345
2346 static void
2347 clutter_text_get_preferred_height (ClutterActor *self,
2348                                    gfloat        for_width,
2349                                    gfloat       *min_height_p,
2350                                    gfloat       *natural_height_p)
2351 {
2352   ClutterTextPrivate *priv = CLUTTER_TEXT (self)->priv;
2353
2354   if (for_width == 0)
2355     {
2356       if (min_height_p)
2357         *min_height_p = 0;
2358
2359       if (natural_height_p)
2360         *natural_height_p = 0;
2361     }
2362   else
2363     {
2364       PangoLayout *layout;
2365       PangoRectangle logical_rect = { 0, };
2366       gint logical_height;
2367       gfloat layout_height;
2368
2369       if (priv->single_line_mode)
2370         for_width = -1;
2371
2372       layout = clutter_text_create_layout (CLUTTER_TEXT (self),
2373                                            for_width, -1);
2374
2375       pango_layout_get_extents (layout, NULL, &logical_rect);
2376
2377       /* the Y coordinate of the logical rectangle might be non-zero
2378        * according to the Pango documentation; hence, we need to offset
2379        * the height accordingly
2380        */
2381       logical_height = logical_rect.y + logical_rect.height;
2382       layout_height = ceilf (logical_height / 1024.0f);
2383
2384       if (min_height_p)
2385         {
2386           /* if we wrap and ellipsize then the minimum height is
2387            * going to be at least the size of the first line
2388            */
2389           if ((priv->ellipsize && priv->wrap) && !priv->single_line_mode)
2390             {
2391               PangoLayoutLine *line;
2392               gfloat line_height;
2393
2394               line = pango_layout_get_line_readonly (layout, 0);
2395               pango_layout_line_get_extents (line, NULL, &logical_rect);
2396
2397               logical_height = logical_rect.y + logical_rect.height;
2398               line_height = ceilf (logical_height / 1024.0f);
2399
2400               *min_height_p = line_height;
2401             }
2402           else
2403             *min_height_p = layout_height;
2404         }
2405
2406       if (natural_height_p)
2407         *natural_height_p = layout_height;
2408     }
2409 }
2410
2411 static void
2412 clutter_text_allocate (ClutterActor           *self,
2413                        const ClutterActorBox  *box,
2414                        ClutterAllocationFlags  flags)
2415 {
2416   ClutterText *text = CLUTTER_TEXT (self);
2417   ClutterActorClass *parent_class;
2418
2419   /* Ensure that there is a cached layout with the right width so
2420    * that we don't need to create the text during the paint run
2421    *
2422    * if the Text is editable and in single line mode we don't want
2423    * to have any limit on the layout size, since the paint will clip
2424    * it to the allocation of the actor
2425    */
2426   if (text->priv->editable && text->priv->single_line_mode)
2427     clutter_text_create_layout (text, -1, -1);
2428   else
2429     clutter_text_create_layout (text,
2430                                 box->x2 - box->x1,
2431                                 box->y2 - box->y1);
2432
2433   parent_class = CLUTTER_ACTOR_CLASS (clutter_text_parent_class);
2434   parent_class->allocate (self, box, flags);
2435 }
2436
2437 static gboolean
2438 clutter_text_has_overlaps (ClutterActor *self)
2439 {
2440   ClutterTextPrivate *priv = CLUTTER_TEXT (self)->priv;
2441
2442   return priv->editable ||
2443          priv->selectable ||
2444          priv->cursor_visible;
2445 }
2446
2447 static void
2448 clutter_text_key_focus_in (ClutterActor *actor)
2449 {
2450   ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv;
2451
2452   priv->has_focus = TRUE;
2453
2454   clutter_text_queue_redraw (actor);
2455 }
2456
2457 static void
2458 clutter_text_key_focus_out (ClutterActor *actor)
2459 {
2460   ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv;
2461
2462   priv->has_focus = FALSE;
2463
2464   clutter_text_queue_redraw (actor);
2465 }
2466
2467 static gboolean
2468 clutter_text_real_move_left (ClutterText         *self,
2469                              const gchar         *action,
2470                              guint                keyval,
2471                              ClutterModifierType  modifiers)
2472 {
2473   ClutterTextPrivate *priv = self->priv;
2474   gint pos = priv->position;
2475   gint new_pos = 0;
2476   gint len;
2477
2478   len = clutter_text_buffer_get_length (get_buffer (self));
2479
2480   g_object_freeze_notify (G_OBJECT (self));
2481
2482   if (pos != 0 && len != 0)
2483     {
2484       if (modifiers & CLUTTER_CONTROL_MASK)
2485         {
2486           if (pos == -1)
2487             new_pos = clutter_text_move_word_backward (self, len);
2488           else
2489             new_pos = clutter_text_move_word_backward (self, pos);
2490         }
2491       else
2492         {
2493           if (pos == -1)
2494             new_pos = len - 1;
2495           else
2496             new_pos = pos - 1;
2497         }
2498
2499       clutter_text_set_cursor_position (self, new_pos);
2500     }
2501
2502   if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
2503     clutter_text_clear_selection (self);
2504
2505   g_object_thaw_notify (G_OBJECT (self));
2506
2507   return TRUE;
2508 }
2509
2510 static gboolean
2511 clutter_text_real_move_right (ClutterText         *self,
2512                               const gchar         *action,
2513                               guint                keyval,
2514                               ClutterModifierType  modifiers)
2515 {
2516   ClutterTextPrivate *priv = self->priv;
2517   gint pos = priv->position;
2518   gint len = clutter_text_buffer_get_length (get_buffer (self));
2519   gint new_pos = 0;
2520
2521   g_object_freeze_notify (G_OBJECT (self));
2522
2523   if (pos != -1 && len !=0)
2524     {
2525       if (modifiers & CLUTTER_CONTROL_MASK)
2526         {
2527           if (pos != len)
2528             new_pos = clutter_text_move_word_forward (self, pos);
2529         }
2530       else
2531         {
2532           if (pos != len)
2533             new_pos = pos + 1;
2534         }
2535
2536       clutter_text_set_cursor_position (self, new_pos);
2537     }
2538
2539   if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
2540     clutter_text_clear_selection (self);
2541
2542   g_object_thaw_notify (G_OBJECT (self));
2543
2544   return TRUE;
2545 }
2546
2547 static gboolean
2548 clutter_text_real_move_up (ClutterText         *self,
2549                            const gchar         *action,
2550                            guint                keyval,
2551                            ClutterModifierType  modifiers)
2552 {
2553   ClutterTextPrivate *priv = self->priv;
2554   PangoLayoutLine *layout_line;
2555   PangoLayout *layout;
2556   gint line_no;
2557   gint index_, trailing;
2558   gint pos;
2559   gint x;
2560   const gchar *text;
2561
2562   layout = clutter_text_get_layout (self);
2563   text = clutter_text_buffer_get_text (get_buffer (self));
2564
2565   if (priv->position == 0)
2566     index_ = 0;
2567   else
2568     index_ = offset_to_bytes (text, priv->position);
2569
2570   pango_layout_index_to_line_x (layout, index_,
2571                                 0,
2572                                 &line_no, &x);
2573
2574   line_no -= 1;
2575   if (line_no < 0)
2576     return FALSE;
2577
2578   if (priv->x_pos != -1)
2579     x = priv->x_pos;
2580
2581   layout_line = pango_layout_get_line_readonly (layout, line_no);
2582   if (!layout_line)
2583     return FALSE;
2584
2585   pango_layout_line_x_to_index (layout_line, x, &index_, &trailing);
2586
2587   g_object_freeze_notify (G_OBJECT (self));
2588
2589   pos = bytes_to_offset (text, index_);
2590   clutter_text_set_cursor_position (self, pos + trailing);
2591
2592   /* Store the target x position to avoid drifting left and right when
2593      moving the cursor up and down */
2594   priv->x_pos = x;
2595
2596   if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
2597     clutter_text_clear_selection (self);
2598
2599   g_object_thaw_notify (G_OBJECT (self));
2600
2601   return TRUE;
2602 }
2603
2604 static gboolean
2605 clutter_text_real_move_down (ClutterText         *self,
2606                              const gchar         *action,
2607                              guint                keyval,
2608                              ClutterModifierType  modifiers)
2609 {
2610   ClutterTextPrivate *priv = self->priv;
2611   PangoLayoutLine *layout_line;
2612   PangoLayout *layout;
2613   gint line_no;
2614   gint index_, trailing;
2615   gint x;
2616   gint pos;
2617   const gchar *text;
2618
2619   layout = clutter_text_get_layout (self);
2620   text = clutter_text_buffer_get_text (get_buffer (self));
2621
2622   if (priv->position == 0)
2623     index_ = 0;
2624   else
2625     index_ = offset_to_bytes (text, priv->position);
2626
2627   pango_layout_index_to_line_x (layout, index_,
2628                                 0,
2629                                 &line_no, &x);
2630
2631   if (priv->x_pos != -1)
2632     x = priv->x_pos;
2633
2634   layout_line = pango_layout_get_line_readonly (layout, line_no + 1);
2635   if (!layout_line)
2636     return FALSE;
2637
2638   pango_layout_line_x_to_index (layout_line, x, &index_, &trailing);
2639
2640   g_object_freeze_notify (G_OBJECT (self));
2641
2642   pos = bytes_to_offset (text, index_);
2643   clutter_text_set_cursor_position (self, pos + trailing);
2644
2645   /* Store the target x position to avoid drifting left and right when
2646      moving the cursor up and down */
2647   priv->x_pos = x;
2648
2649   if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
2650     clutter_text_clear_selection (self);
2651
2652   g_object_thaw_notify (G_OBJECT (self));
2653
2654   return TRUE;
2655 }
2656
2657 static gboolean
2658 clutter_text_real_line_start (ClutterText         *self,
2659                               const gchar         *action,
2660                               guint                keyval,
2661                               ClutterModifierType  modifiers)
2662 {
2663   ClutterTextPrivate *priv = self->priv;
2664   gint position;
2665
2666   g_object_freeze_notify (G_OBJECT (self));
2667
2668   position = clutter_text_move_line_start (self, priv->position);
2669   clutter_text_set_cursor_position (self, position);
2670
2671   if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
2672     clutter_text_clear_selection (self);
2673
2674   g_object_thaw_notify (G_OBJECT (self));
2675
2676   return TRUE;
2677 }
2678
2679 static gboolean
2680 clutter_text_real_line_end (ClutterText         *self,
2681                             const gchar         *action,
2682                             guint                keyval,
2683                             ClutterModifierType  modifiers)
2684 {
2685   ClutterTextPrivate *priv = self->priv;
2686   gint position;
2687
2688   g_object_freeze_notify (G_OBJECT (self));
2689
2690   position = clutter_text_move_line_end (self, priv->position);
2691   clutter_text_set_cursor_position (self, position);
2692
2693   if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
2694     clutter_text_clear_selection (self);
2695
2696   g_object_thaw_notify (G_OBJECT (self));
2697
2698   return TRUE;
2699 }
2700
2701 static gboolean
2702 clutter_text_real_select_all (ClutterText         *self,
2703                               const gchar         *action,
2704                               guint                keyval,
2705                               ClutterModifierType  modifiers)
2706 {
2707   guint n_chars = clutter_text_buffer_get_length (get_buffer (self));
2708   clutter_text_set_positions (self, 0, n_chars);
2709
2710   return TRUE;
2711 }
2712
2713 static gboolean
2714 clutter_text_real_del_next (ClutterText         *self,
2715                             const gchar         *action,
2716                             guint                keyval,
2717                             ClutterModifierType  modifiers)
2718 {
2719   ClutterTextPrivate *priv = self->priv;
2720   gint pos;
2721   gint len;
2722
2723   if (clutter_text_delete_selection (self))
2724     return TRUE;
2725
2726   pos = priv->position;
2727   len = clutter_text_buffer_get_length (get_buffer (self));
2728
2729   if (len && pos != -1 && pos < len)
2730     clutter_text_delete_text (self, pos, pos + 1);
2731
2732   return TRUE;
2733 }
2734
2735 static gboolean
2736 clutter_text_real_del_word_next (ClutterText         *self,
2737                                  const gchar         *action,
2738                                  guint                keyval,
2739                                  ClutterModifierType  modifiers)
2740 {
2741   ClutterTextPrivate *priv = self->priv;
2742   gint pos;
2743   gint len;
2744
2745   pos = priv->position;
2746   len = clutter_text_buffer_get_length (get_buffer (self));
2747
2748   if (len && pos != -1 && pos < len)
2749     {
2750       gint end;
2751
2752       end = clutter_text_move_word_forward (self, pos);
2753       clutter_text_delete_text (self, pos, end);
2754
2755       if (priv->selection_bound >= end)
2756         {
2757           gint new_bound;
2758
2759           new_bound = priv->selection_bound - (end - pos);
2760           clutter_text_set_selection_bound (self, new_bound);
2761         }
2762       else if (priv->selection_bound > pos)
2763         {
2764           clutter_text_set_selection_bound (self, pos);
2765         }
2766     }
2767
2768   return TRUE;
2769 }
2770
2771 static gboolean
2772 clutter_text_real_del_prev (ClutterText         *self,
2773                             const gchar         *action,
2774                             guint                keyval,
2775                             ClutterModifierType  modifiers)
2776 {
2777   ClutterTextPrivate *priv = self->priv;
2778   gint pos;
2779   gint len;
2780
2781   if (clutter_text_delete_selection (self))
2782     return TRUE;
2783
2784   pos = priv->position;
2785   len = clutter_text_buffer_get_length (get_buffer (self));
2786
2787   if (pos != 0 && len != 0)
2788     {
2789       if (pos == -1)
2790         {
2791           clutter_text_delete_text (self, len - 1, len);
2792
2793           clutter_text_set_positions (self, -1, -1);
2794         }
2795       else
2796         {
2797           clutter_text_delete_text (self, pos - 1, pos);
2798
2799           clutter_text_set_positions (self, pos - 1, pos - 1);
2800         }
2801     }
2802
2803   return TRUE;
2804 }
2805
2806 static gboolean
2807 clutter_text_real_del_word_prev (ClutterText         *self,
2808                                  const gchar         *action,
2809                                  guint                keyval,
2810                                  ClutterModifierType  modifiers)
2811 {
2812   ClutterTextPrivate *priv = self->priv;
2813   gint pos;
2814   gint len;
2815
2816   pos = priv->position;
2817   len = clutter_text_buffer_get_length (get_buffer (self));
2818
2819   if (pos != 0 && len != 0)
2820     {
2821       gint new_pos;
2822
2823       if (pos == -1)
2824         {
2825           new_pos = clutter_text_move_word_backward (self, len);
2826           clutter_text_delete_text (self, new_pos, len);
2827
2828           clutter_text_set_positions (self, -1, -1);
2829         }
2830       else
2831         {
2832           new_pos = clutter_text_move_word_backward (self, pos);
2833           clutter_text_delete_text (self, new_pos, pos);
2834
2835           clutter_text_set_cursor_position (self, new_pos);
2836           if (priv->selection_bound >= pos)
2837             {
2838               gint new_bound;
2839
2840               new_bound = priv->selection_bound - (pos - new_pos);
2841               clutter_text_set_selection_bound (self, new_bound);
2842             }
2843           else if (priv->selection_bound >= new_pos)
2844             {
2845               clutter_text_set_selection_bound (self, new_pos);
2846             }
2847         }
2848     }
2849
2850   return TRUE;
2851 }
2852
2853 static gboolean
2854 clutter_text_real_activate (ClutterText         *self,
2855                             const gchar         *action,
2856                             guint                keyval,
2857                             ClutterModifierType  modifiers)
2858 {
2859   return clutter_text_activate (self);
2860 }
2861
2862 static inline void
2863 clutter_text_add_move_binding (ClutterBindingPool  *pool,
2864                                const gchar         *action,
2865                                guint                key_val,
2866                                ClutterModifierType  additional_modifiers,
2867                                GCallback            callback)
2868 {
2869   clutter_binding_pool_install_action (pool, action,
2870                                        key_val,
2871                                        0,
2872                                        callback,
2873                                        NULL, NULL);
2874   clutter_binding_pool_install_action (pool, action,
2875                                        key_val,
2876                                        CLUTTER_SHIFT_MASK,
2877                                        callback,
2878                                        NULL, NULL);
2879
2880   if (additional_modifiers != 0)
2881     {
2882       clutter_binding_pool_install_action (pool, action,
2883                                            key_val,
2884                                            additional_modifiers,
2885                                            callback,
2886                                            NULL, NULL);
2887       clutter_binding_pool_install_action (pool, action,
2888                                            key_val,
2889                                            CLUTTER_SHIFT_MASK |
2890                                              additional_modifiers,
2891                                            callback,
2892                                            NULL, NULL);
2893     }
2894 }
2895
2896 static gboolean
2897 clutter_text_parse_custom_node (ClutterScriptable *scriptable,
2898                                 ClutterScript     *script,
2899                                 GValue            *value,
2900                                 const gchar       *name,
2901                                 JsonNode          *node)
2902 {
2903   if (strncmp (name, "font-description", 16) == 0)
2904     {
2905       g_value_init (value, G_TYPE_STRING);
2906       g_value_set_string (value, json_node_get_string (node));
2907
2908       return TRUE;
2909     }
2910
2911   return FALSE;
2912 }
2913
2914 static void
2915 clutter_text_set_color_internal (ClutterText        *self,
2916                                  GParamSpec         *pspec,
2917                                  const ClutterColor *color)
2918 {
2919   ClutterTextPrivate *priv = CLUTTER_TEXT (self)->priv;
2920   GParamSpec *other = NULL;
2921
2922   switch (pspec->param_id)
2923     {
2924     case PROP_COLOR:
2925       priv->text_color = *color;
2926       break;
2927
2928     case PROP_CURSOR_COLOR:
2929       if (color)
2930         {
2931           priv->cursor_color = *color;
2932           priv->cursor_color_set = TRUE;
2933         }
2934       else
2935         priv->cursor_color_set = FALSE;
2936
2937       other = obj_props[PROP_CURSOR_COLOR_SET];
2938       break;
2939
2940     case PROP_SELECTION_COLOR:
2941       if (color)
2942         {
2943           priv->selection_color = *color;
2944           priv->selection_color_set = TRUE;
2945         }
2946       else
2947         priv->selection_color_set = FALSE;
2948
2949       other = obj_props[PROP_SELECTION_COLOR_SET];
2950       break;
2951
2952     case PROP_SELECTED_TEXT_COLOR:
2953       if (color)
2954         {
2955           priv->selected_text_color = *color;
2956           priv->selected_text_color_set = TRUE;
2957         }
2958       else
2959         priv->selected_text_color_set = FALSE;
2960
2961       other = obj_props[PROP_SELECTED_TEXT_COLOR_SET];
2962       break;
2963
2964     default:
2965       g_assert_not_reached ();
2966       break;
2967     }
2968
2969   clutter_text_queue_redraw (CLUTTER_ACTOR (self));
2970   g_object_notify_by_pspec (G_OBJECT (self), pspec);
2971   if (other)
2972     g_object_notify_by_pspec (G_OBJECT (self), other);
2973 }
2974
2975 static void
2976 clutter_text_set_color_animated (ClutterText        *self,
2977                                  GParamSpec         *pspec,
2978                                  const ClutterColor *color)
2979 {
2980   ClutterActor *actor = CLUTTER_ACTOR (self);
2981   ClutterTextPrivate *priv = self->priv;
2982   const ClutterAnimationInfo *info;
2983   ClutterTransition *transition;
2984
2985   info = _clutter_actor_get_animation_info (actor);
2986   transition = clutter_actor_get_transition (actor, pspec->name);
2987
2988   /* jump to the end if there is no easing state, or if the easing
2989    * state has a duration of 0 msecs
2990    */
2991   if (info->cur_state == NULL ||
2992       info->cur_state->easing_duration == 0)
2993     {
2994       /* ensure that we remove any currently running transition */
2995       if (transition != NULL)
2996         {
2997           clutter_actor_remove_transition (actor, pspec->name);
2998           transition = NULL;
2999         }
3000
3001       clutter_text_set_color_internal (self, pspec, color);
3002
3003       return;
3004     }
3005
3006   if (transition == NULL)
3007     {
3008       transition = clutter_property_transition_new (pspec->name);
3009       clutter_transition_set_animatable (transition,
3010                                          CLUTTER_ANIMATABLE (self));
3011       clutter_transition_set_remove_on_complete (transition, TRUE);
3012
3013       /* delay only makes sense if the transition has just been created */
3014       clutter_timeline_set_delay (CLUTTER_TIMELINE (transition),
3015                                   info->cur_state->easing_delay);
3016
3017       clutter_actor_add_transition (actor, pspec->name, transition);
3018
3019       /* the actor now owns the transition */
3020       g_object_unref (transition);
3021     }
3022
3023   /* if a transition already exist, update its bounds */
3024   switch (pspec->param_id)
3025     {
3026     case PROP_COLOR:
3027       clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR,
3028                                    &priv->text_color);
3029       break;
3030
3031     case PROP_CURSOR_COLOR:
3032       clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR,
3033                                    &priv->cursor_color);
3034       break;
3035
3036     case PROP_SELECTION_COLOR:
3037       clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR,
3038                                    &priv->selection_color);
3039       break;
3040
3041     case PROP_SELECTED_TEXT_COLOR:
3042       clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR,
3043                                    &priv->selected_text_color);
3044       break;
3045
3046     default:
3047       g_assert_not_reached ();
3048     }
3049
3050   clutter_transition_set_to (transition, CLUTTER_TYPE_COLOR, color);
3051
3052   /* always use the current easing state */
3053   clutter_timeline_set_duration (CLUTTER_TIMELINE (transition),
3054                                  info->cur_state->easing_duration);
3055   clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (transition),
3056                                       info->cur_state->easing_mode);
3057
3058   /* ensure that we start from the beginning */
3059   clutter_timeline_rewind (CLUTTER_TIMELINE (transition));
3060   clutter_timeline_start (CLUTTER_TIMELINE (transition));
3061 }
3062
3063 static void
3064 clutter_text_set_custom_property (ClutterScriptable *scriptable,
3065                                   ClutterScript     *script,
3066                                   const gchar       *name,
3067                                   const GValue      *value)
3068 {
3069   if (strncmp (name, "font-description", 16) == 0)
3070     {
3071       g_assert (G_VALUE_HOLDS (value, G_TYPE_STRING));
3072       if (g_value_get_string (value) != NULL)
3073         clutter_text_set_font_name (CLUTTER_TEXT (scriptable),
3074                                     g_value_get_string (value));
3075     }
3076   else
3077     g_object_set_property (G_OBJECT (scriptable), name, value);
3078 }
3079
3080 static void
3081 clutter_scriptable_iface_init (ClutterScriptableIface *iface)
3082 {
3083   iface->parse_custom_node = clutter_text_parse_custom_node;
3084   iface->set_custom_property = clutter_text_set_custom_property;
3085 }
3086
3087 static void
3088 clutter_text_set_final_state (ClutterAnimatable *animatable,
3089                               const char        *property_name,
3090                               const GValue      *value)
3091 {
3092   if (strcmp (property_name, "color") == 0)
3093     {
3094       const ClutterColor *color = clutter_value_get_color (value);
3095       clutter_text_set_color_internal (CLUTTER_TEXT (animatable),
3096                                        obj_props[PROP_COLOR], color);
3097     }
3098   else if (strcmp (property_name, "cursor-color") == 0)
3099     {
3100       const ClutterColor *color = clutter_value_get_color (value);
3101       clutter_text_set_color_internal (CLUTTER_TEXT (animatable),
3102                                        obj_props[PROP_CURSOR_COLOR],
3103                                        color);
3104     }
3105   else if (strcmp (property_name, "selected-text-color") == 0)
3106     {
3107       const ClutterColor *color = clutter_value_get_color (value);
3108       clutter_text_set_color_internal (CLUTTER_TEXT (animatable),
3109                                        obj_props[PROP_SELECTED_TEXT_COLOR],
3110                                        color);
3111     }
3112   else if (strcmp (property_name, "selection-color") == 0)
3113     {
3114       const ClutterColor *color = clutter_value_get_color (value);
3115       clutter_text_set_color_internal (CLUTTER_TEXT (animatable),
3116                                        obj_props[PROP_SELECTION_COLOR],
3117                                        color);
3118     }
3119   else
3120     parent_animatable_iface->set_final_state (animatable, property_name, value);
3121 }
3122
3123 static void
3124 clutter_animatable_iface_init (ClutterAnimatableIface *iface)
3125 {
3126   parent_animatable_iface = g_type_interface_peek_parent (iface);
3127
3128   iface->set_final_state = clutter_text_set_final_state;
3129 }
3130
3131 static void
3132 clutter_text_class_init (ClutterTextClass *klass)
3133 {
3134   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
3135   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
3136   ClutterBindingPool *binding_pool;
3137   GParamSpec *pspec;
3138
3139   g_type_class_add_private (klass, sizeof (ClutterTextPrivate));
3140
3141   gobject_class->set_property = clutter_text_set_property;
3142   gobject_class->get_property = clutter_text_get_property;
3143   gobject_class->dispose = clutter_text_dispose;
3144   gobject_class->finalize = clutter_text_finalize;
3145
3146   actor_class->paint = clutter_text_paint;
3147   actor_class->get_paint_volume = clutter_text_get_paint_volume;
3148   actor_class->get_preferred_width = clutter_text_get_preferred_width;
3149   actor_class->get_preferred_height = clutter_text_get_preferred_height;
3150   actor_class->allocate = clutter_text_allocate;
3151   actor_class->key_press_event = clutter_text_key_press;
3152   actor_class->button_press_event = clutter_text_button_press;
3153   actor_class->button_release_event = clutter_text_button_release;
3154   actor_class->motion_event = clutter_text_motion;
3155   actor_class->key_focus_in = clutter_text_key_focus_in;
3156   actor_class->key_focus_out = clutter_text_key_focus_out;
3157   actor_class->has_overlaps = clutter_text_has_overlaps;
3158
3159   /**
3160    * ClutterText:buffer:
3161    *
3162    * The buffer which stores the text for this #ClutterText.
3163    *
3164    * If set to %NULL, a default buffer will be created.
3165    *
3166    * Since: 1.8
3167    */
3168   pspec = g_param_spec_object ("buffer",
3169                                P_("Buffer"),
3170                                P_("The buffer for the text"),
3171                                CLUTTER_TYPE_TEXT_BUFFER,
3172                                CLUTTER_PARAM_READWRITE);
3173   obj_props[PROP_BUFFER] = pspec;
3174   g_object_class_install_property (gobject_class, PROP_BUFFER, pspec);
3175
3176   /**
3177    * ClutterText:font-name:
3178    *
3179    * The font to be used by the #ClutterText, as a string
3180    * that can be parsed by pango_font_description_from_string().
3181    *
3182    * If set to %NULL, the default system font will be used instead.
3183    *
3184    * Since: 1.0
3185    */
3186   pspec = g_param_spec_string ("font-name",
3187                                P_("Font Name"),
3188                                P_("The font to be used by the text"),
3189                                NULL,
3190                                CLUTTER_PARAM_READWRITE);
3191   obj_props[PROP_FONT_NAME] = pspec;
3192   g_object_class_install_property (gobject_class, PROP_FONT_NAME, pspec);
3193
3194   /**
3195    * ClutterText:font-description:
3196    *
3197    * The #PangoFontDescription that should be used by the #ClutterText
3198    *
3199    * If you have a string describing the font then you should look at
3200    * #ClutterText:font-name instead
3201    *
3202    * Since: 1.2
3203    */
3204   pspec = g_param_spec_boxed ("font-description",
3205                               P_("Font Description"),
3206                               P_("The font description to be used"),
3207                               PANGO_TYPE_FONT_DESCRIPTION,
3208                               CLUTTER_PARAM_READWRITE);
3209   obj_props[PROP_FONT_DESCRIPTION] = pspec;
3210   g_object_class_install_property (gobject_class,
3211                                    PROP_FONT_DESCRIPTION,
3212                                    pspec);
3213
3214   /**
3215    * ClutterText:text:
3216    *
3217    * The text to render inside the actor.
3218    *
3219    * Since: 1.0
3220    */
3221   pspec = g_param_spec_string ("text",
3222                                P_("Text"),
3223                                P_("The text to render"),
3224                                "",
3225                                CLUTTER_PARAM_READWRITE);
3226   obj_props[PROP_TEXT] = pspec;
3227   g_object_class_install_property (gobject_class, PROP_TEXT, pspec);
3228
3229   /**
3230    * ClutterText:color:
3231    *
3232    * The color used to render the text.
3233    *
3234    * Since: 1.0
3235    */
3236   pspec = clutter_param_spec_color ("color",
3237                                     P_("Font Color"),
3238                                     P_("Color of the font used by the text"),
3239                                     &default_text_color,
3240                                     CLUTTER_PARAM_READWRITE |
3241                                     CLUTTER_PARAM_ANIMATABLE);
3242   obj_props[PROP_COLOR] = pspec;
3243   g_object_class_install_property (gobject_class, PROP_COLOR, pspec);
3244
3245   /**
3246    * ClutterText:editable:
3247    *
3248    * Whether key events delivered to the actor causes editing.
3249    *
3250    * Since: 1.0
3251    */
3252   pspec = g_param_spec_boolean ("editable",
3253                                 P_("Editable"),
3254                                 P_("Whether the text is editable"),
3255                                 FALSE,
3256                                 G_PARAM_READWRITE);
3257   obj_props[PROP_EDITABLE] = pspec;
3258   g_object_class_install_property (gobject_class, PROP_EDITABLE, pspec);
3259
3260   /**
3261    * ClutterText:selectable:
3262    *
3263    * Whether it is possible to select text, either using the pointer
3264    * or the keyboard.
3265    *
3266    * Since: 1.0
3267    */
3268   pspec = g_param_spec_boolean ("selectable",
3269                                 P_("Selectable"),
3270                                 P_("Whether the text is selectable"),
3271                                 TRUE,
3272                                 G_PARAM_READWRITE);
3273   obj_props[PROP_SELECTABLE] = pspec;
3274   g_object_class_install_property (gobject_class, PROP_SELECTABLE, pspec);
3275
3276   /**
3277    * ClutterText:activatable:
3278    *
3279    * Toggles whether return invokes the activate signal or not.
3280    *
3281    * Since: 1.0
3282    */
3283   pspec = g_param_spec_boolean ("activatable",
3284                                 P_("Activatable"),
3285                                 P_("Whether pressing return causes the activate signal to be emitted"),
3286                                 TRUE,
3287                                 G_PARAM_READWRITE);
3288   obj_props[PROP_ACTIVATABLE] = pspec;
3289   g_object_class_install_property (gobject_class, PROP_ACTIVATABLE, pspec);
3290
3291   /**
3292    * ClutterText:cursor-visible:
3293    *
3294    * Whether the input cursor is visible or not, it will only be visible
3295    * if both #ClutterText:cursor-visible and #ClutterText:editable are
3296    * set to %TRUE.
3297    *
3298    * Since: 1.0
3299    */
3300   pspec = g_param_spec_boolean ("cursor-visible",
3301                                 P_("Cursor Visible"),
3302                                 P_("Whether the input cursor is visible"),
3303                                 TRUE,
3304                                 CLUTTER_PARAM_READWRITE);
3305   obj_props[PROP_CURSOR_VISIBLE] = pspec;
3306   g_object_class_install_property (gobject_class, PROP_CURSOR_VISIBLE, pspec);
3307
3308   /**
3309    * ClutterText:cursor-color:
3310    *
3311    * The color of the cursor.
3312    *
3313    * Since: 1.0
3314    */
3315   pspec = clutter_param_spec_color ("cursor-color",
3316                                     P_("Cursor Color"),
3317                                     P_("Cursor Color"),
3318                                     &default_cursor_color,
3319                                     CLUTTER_PARAM_READWRITE |
3320                                     CLUTTER_PARAM_ANIMATABLE);
3321   obj_props[PROP_CURSOR_COLOR] = pspec;
3322   g_object_class_install_property (gobject_class, PROP_CURSOR_COLOR, pspec);
3323
3324   /**
3325    * ClutterText:cursor-color-set:
3326    *
3327    * Will be set to %TRUE if #ClutterText:cursor-color has been set.
3328    *
3329    * Since: 1.0
3330    */
3331   pspec = g_param_spec_boolean ("cursor-color-set",
3332                                 P_("Cursor Color Set"),
3333                                 P_("Whether the cursor color has been set"),
3334                                 FALSE,
3335                                 CLUTTER_PARAM_READABLE);
3336   obj_props[PROP_CURSOR_COLOR_SET] = pspec;
3337   g_object_class_install_property (gobject_class, PROP_CURSOR_COLOR_SET, pspec);
3338
3339   /**
3340    * ClutterText:cursor-size:
3341    *
3342    * The size of the cursor, in pixels. If set to -1 the size used will
3343    * be the default cursor size of 2 pixels.
3344    *
3345    * Since: 1.0
3346    */
3347   pspec = g_param_spec_int ("cursor-size",
3348                             P_("Cursor Size"),
3349                             P_("The width of the cursor, in pixels"),
3350                             -1, G_MAXINT, DEFAULT_CURSOR_SIZE,
3351                             CLUTTER_PARAM_READWRITE);
3352   obj_props[PROP_CURSOR_SIZE] = pspec;
3353   g_object_class_install_property (gobject_class, PROP_CURSOR_SIZE, pspec);
3354
3355   /**
3356    * ClutterText:position:
3357    *
3358    * The current input cursor position. -1 is taken to be the end of the text
3359    *
3360    * Since: 1.0
3361    */
3362   pspec = g_param_spec_int ("position",
3363                             P_("Cursor Position"),
3364                             P_("The cursor position"),
3365                             -1, G_MAXINT,
3366                             -1,
3367                             CLUTTER_PARAM_READWRITE);
3368   obj_props[PROP_POSITION] = pspec;
3369   g_object_class_install_property (gobject_class, PROP_POSITION, pspec);
3370
3371   /**
3372    * ClutterText:selection-bound:
3373    *
3374    * The current input cursor position. -1 is taken to be the end of the text
3375    *
3376    * Since: 1.0
3377    */
3378   pspec = g_param_spec_int ("selection-bound",
3379                             P_("Selection-bound"),
3380                             P_("The cursor position of the other end of the selection"),
3381                             -1, G_MAXINT,
3382                             -1,
3383                             CLUTTER_PARAM_READWRITE);
3384   obj_props[PROP_SELECTION_BOUND] = pspec;
3385   g_object_class_install_property (gobject_class, PROP_SELECTION_BOUND, pspec);
3386
3387   /**
3388    * ClutterText:selection-color:
3389    *
3390    * The color of the selection.
3391    *
3392    * Since: 1.0
3393    */
3394   pspec = clutter_param_spec_color ("selection-color",
3395                                     P_("Selection Color"),
3396                                     P_("Selection Color"),
3397                                     &default_selection_color,
3398                                     CLUTTER_PARAM_READWRITE |
3399                                     CLUTTER_PARAM_ANIMATABLE);
3400   obj_props[PROP_SELECTION_COLOR] = pspec;
3401   g_object_class_install_property (gobject_class, PROP_SELECTION_COLOR, pspec);
3402
3403   /**
3404    * ClutterText:selection-color-set:
3405    *
3406    * Will be set to %TRUE if #ClutterText:selection-color has been set.
3407    *
3408    * Since: 1.0
3409    */
3410   pspec = g_param_spec_boolean ("selection-color-set",
3411                                 P_("Selection Color Set"),
3412                                 P_("Whether the selection color has been set"),
3413                                 FALSE,
3414                                 CLUTTER_PARAM_READABLE);
3415   obj_props[PROP_SELECTION_COLOR_SET] = pspec;
3416   g_object_class_install_property (gobject_class, PROP_SELECTION_COLOR_SET, pspec);
3417
3418   /**
3419    * ClutterText:attributes:
3420    *
3421    * A list of #PangoStyleAttribute<!-- -->s to be applied to the
3422    * contents of the #ClutterText actor.
3423    *
3424    * Since: 1.0
3425    */
3426   pspec = g_param_spec_boxed ("attributes",
3427                               P_("Attributes"),
3428                               P_("A list of style attributes to apply to the contents of the actor"),
3429                               PANGO_TYPE_ATTR_LIST,
3430                               CLUTTER_PARAM_READWRITE);
3431   obj_props[PROP_ATTRIBUTES] = pspec;
3432   g_object_class_install_property (gobject_class, PROP_ATTRIBUTES, pspec);
3433
3434   /**
3435    * ClutterText:use-markup:
3436    *
3437    * Whether the text includes Pango markup.
3438    *
3439    * For more informations about the Pango markup format, see
3440    * pango_layout_set_markup() in the Pango documentation.
3441    *
3442    * <note>It is not possible to round-trip this property between
3443    * %TRUE and %FALSE. Once a string with markup has been set on
3444    * a #ClutterText actor with :use-markup set to %TRUE, the markup
3445    * is stripped from the string.</note>
3446    *
3447    * Since: 1.0
3448    */
3449   pspec = g_param_spec_boolean ("use-markup",
3450                                 P_("Use markup"),
3451                                 P_("Whether or not the text includes Pango markup"),
3452                                 FALSE,
3453                                 CLUTTER_PARAM_READWRITE);
3454   obj_props[PROP_USE_MARKUP] = pspec;
3455   g_object_class_install_property (gobject_class, PROP_USE_MARKUP, pspec);
3456
3457   /**
3458    * ClutterText:line-wrap:
3459    *
3460    * Whether to wrap the lines of #ClutterText:text if the contents
3461    * exceed the available allocation. The wrapping strategy is
3462    * controlled by the #ClutterText:line-wrap-mode property.
3463    *
3464    * Since: 1.0
3465    */
3466   pspec = g_param_spec_boolean ("line-wrap",
3467                                 P_("Line wrap"),
3468                                 P_("If set, wrap the lines if the text becomes too wide"),
3469                                 FALSE,
3470                                 CLUTTER_PARAM_READWRITE);
3471   obj_props[PROP_LINE_WRAP] = pspec;
3472   g_object_class_install_property (gobject_class, PROP_LINE_WRAP, pspec);
3473
3474   /**
3475    * ClutterText:line-wrap-mode:
3476    *
3477    * If #ClutterText:line-wrap is set to %TRUE, this property will
3478    * control how the text is wrapped.
3479    *
3480    * Since: 1.0
3481    */
3482   pspec = g_param_spec_enum ("line-wrap-mode",
3483                              P_("Line wrap mode"),
3484                              P_("Control how line-wrapping is done"),
3485                              PANGO_TYPE_WRAP_MODE,
3486                              PANGO_WRAP_WORD,
3487                              CLUTTER_PARAM_READWRITE);
3488   obj_props[PROP_LINE_WRAP_MODE] = pspec;
3489   g_object_class_install_property (gobject_class, PROP_LINE_WRAP_MODE, pspec);
3490
3491   /**
3492    * ClutterText:ellipsize:
3493    *
3494    * The preferred place to ellipsize the contents of the #ClutterText actor
3495    *
3496    * Since: 1.0
3497    */
3498   pspec = g_param_spec_enum ("ellipsize",
3499                              P_("Ellipsize"),
3500                              P_("The preferred place to ellipsize the string"),
3501                              PANGO_TYPE_ELLIPSIZE_MODE,
3502                              PANGO_ELLIPSIZE_NONE,
3503                              CLUTTER_PARAM_READWRITE);
3504   obj_props[PROP_ELLIPSIZE] = pspec;
3505   g_object_class_install_property (gobject_class, PROP_ELLIPSIZE, pspec);
3506
3507   /**
3508    * ClutterText:line-alignment:
3509    *
3510    * The preferred alignment for the text. This property controls
3511    * the alignment of multi-line paragraphs.
3512    *
3513    * Since: 1.0
3514    */
3515   pspec = g_param_spec_enum ("line-alignment",
3516                              P_("Line Alignment"),
3517                              P_("The preferred alignment for the string, for multi-line text"),
3518                              PANGO_TYPE_ALIGNMENT,
3519                              PANGO_ALIGN_LEFT,
3520                              CLUTTER_PARAM_READWRITE);
3521   obj_props[PROP_LINE_ALIGNMENT] = pspec;
3522   g_object_class_install_property (gobject_class, PROP_LINE_ALIGNMENT, pspec);
3523
3524   /**
3525    * ClutterText:justify:
3526    *
3527    * Whether the contents of the #ClutterText should be justified
3528    * on both margins.
3529    *
3530    * Since: 1.0
3531    */
3532   pspec = g_param_spec_boolean ("justify",
3533                                 P_("Justify"),
3534                                 P_("Whether the text should be justified"),
3535                                 FALSE,
3536                                 CLUTTER_PARAM_READWRITE);
3537   obj_props[PROP_JUSTIFY] = pspec;
3538   g_object_class_install_property (gobject_class, PROP_JUSTIFY, pspec);
3539
3540   /**
3541    * ClutterText:password-char:
3542    *
3543    * If non-zero, the character that should be used in place of
3544    * the actual text in a password text actor.
3545    *
3546    * Since: 1.0
3547    */
3548   pspec = g_param_spec_unichar ("password-char",
3549                                 P_("Password Character"),
3550                                 P_("If non-zero, use this character to display the actor's contents"),
3551                                 0,
3552                                 CLUTTER_PARAM_READWRITE);
3553   obj_props[PROP_PASSWORD_CHAR] = pspec;
3554   g_object_class_install_property (gobject_class, PROP_PASSWORD_CHAR, pspec);
3555
3556   /**
3557    * ClutterText:max-length:
3558    *
3559    * The maximum length of the contents of the #ClutterText actor.
3560    *
3561    * Since: 1.0
3562    */
3563   pspec = g_param_spec_int ("max-length",
3564                             P_("Max Length"),
3565                             P_("Maximum length of the text inside the actor"),
3566                             -1, G_MAXINT, 0,
3567                             CLUTTER_PARAM_READWRITE);
3568   obj_props[PROP_MAX_LENGTH] = pspec;
3569   g_object_class_install_property (gobject_class, PROP_MAX_LENGTH, pspec);
3570
3571   /**
3572    * ClutterText:single-line-mode:
3573    *
3574    * Whether the #ClutterText actor should be in single line mode
3575    * or not. A single line #ClutterText actor will only contain a
3576    * single line of text, scrolling it in case its length is bigger
3577    * than the allocated size.
3578    *
3579    * Setting this property will also set the #ClutterText:activatable
3580    * property as a side-effect.
3581    *
3582    * The #ClutterText:single-line-mode property is used only if the
3583    * #ClutterText:editable property is set to %TRUE.
3584    *
3585    * Since: 1.0
3586    */
3587   pspec = g_param_spec_boolean ("single-line-mode",
3588                                 P_("Single Line Mode"),
3589                                 P_("Whether the text should be a single line"),
3590                                 FALSE,
3591                                 CLUTTER_PARAM_READWRITE);
3592   obj_props[PROP_SINGLE_LINE_MODE] = pspec;
3593   g_object_class_install_property (gobject_class, PROP_SINGLE_LINE_MODE, pspec);
3594
3595   /**
3596    * ClutterText:selected-text-color:
3597    *
3598    * The color of selected text.
3599    *
3600    * Since: 1.8
3601    */
3602   pspec = clutter_param_spec_color ("selected-text-color",
3603                                     P_("Selected Text Color"),
3604                                     P_("Selected Text Color"),
3605                                     &default_selected_text_color,
3606                                     CLUTTER_PARAM_READWRITE |
3607                                     CLUTTER_PARAM_ANIMATABLE);
3608   obj_props[PROP_SELECTED_TEXT_COLOR] = pspec;
3609   g_object_class_install_property (gobject_class, PROP_SELECTED_TEXT_COLOR, pspec);
3610
3611   /**
3612    * ClutterText:selected-text-color-set:
3613    *
3614    * Will be set to %TRUE if #ClutterText:selected-text-color has been set.
3615    *
3616    * Since: 1.8
3617    */
3618   pspec = g_param_spec_boolean ("selected-text-color-set",
3619                                 P_("Selected Text Color Set"),
3620                                 P_("Whether the selected text color has been set"),
3621                                 FALSE,
3622                                 CLUTTER_PARAM_READABLE);
3623   obj_props[PROP_SELECTED_TEXT_COLOR_SET] = pspec;
3624   g_object_class_install_property (gobject_class, PROP_SELECTED_TEXT_COLOR_SET, pspec);
3625
3626   /**
3627    * ClutterText::text-changed:
3628    * @self: the #ClutterText that emitted the signal
3629    *
3630    * The ::text-changed signal is emitted after @actor's text changes
3631    *
3632    * Since: 1.0
3633    */
3634   text_signals[TEXT_CHANGED] =
3635     g_signal_new (I_("text-changed"),
3636                   G_TYPE_FROM_CLASS (gobject_class),
3637                   G_SIGNAL_RUN_LAST,
3638                   G_STRUCT_OFFSET (ClutterTextClass, text_changed),
3639                   NULL, NULL,
3640                   _clutter_marshal_VOID__VOID,
3641                   G_TYPE_NONE, 0);
3642
3643   /**
3644    * ClutterText::insert-text:
3645    * @self: the #ClutterText that emitted the signal
3646    * @new_text: the new text to insert
3647    * @new_text_length: the length of the new text, in bytes, or -1 if
3648    *     new_text is nul-terminated
3649    * @position: the position, in characters, at which to insert the
3650    *     new text. this is an in-out parameter.  After the signal
3651    *     emission is finished, it should point after the newly
3652    *     inserted text.
3653    *
3654    * This signal is emitted when text is inserted into the actor by
3655    * the user. It is emitted before @self text changes.
3656    *
3657    * Since: 1.2
3658    */
3659   text_signals[INSERT_TEXT] =
3660     g_signal_new (I_("insert-text"),
3661                   G_TYPE_FROM_CLASS (gobject_class),
3662                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3663                   0,
3664                   NULL, NULL,
3665                   _clutter_marshal_VOID__STRING_INT_POINTER,
3666                   G_TYPE_NONE, 3,
3667                   G_TYPE_STRING,
3668                   G_TYPE_INT,
3669                   G_TYPE_POINTER);
3670
3671   /**
3672    * ClutterText::delete-text:
3673    * @self: the #ClutterText that emitted the signal
3674    * @start_pos: the starting position
3675    * @end_pos: the end position
3676    *
3677    * This signal is emitted when text is deleted from the actor by
3678    * the user. It is emitted before @self text changes.
3679    *
3680    * Since: 1.2
3681    */
3682   text_signals[DELETE_TEXT] =
3683     g_signal_new (I_("delete-text"),
3684                   G_TYPE_FROM_CLASS (gobject_class),
3685                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3686                   0,
3687                   NULL, NULL,
3688                   _clutter_marshal_VOID__INT_INT,
3689                   G_TYPE_NONE, 2,
3690                   G_TYPE_INT,
3691                   G_TYPE_INT);
3692
3693   /**
3694    * ClutterText::cursor-event:
3695    * @self: the #ClutterText that emitted the signal
3696    * @geometry: the coordinates of the cursor
3697    *
3698    * The ::cursor-event signal is emitted whenever the cursor position
3699    * changes inside a #ClutterText actor. Inside @geometry it is stored
3700    * the current position and size of the cursor, relative to the actor
3701    * itself.
3702    *
3703    * Since: 1.0
3704    */
3705   text_signals[CURSOR_EVENT] =
3706     g_signal_new (I_("cursor-event"),
3707                   G_TYPE_FROM_CLASS (gobject_class),
3708                   G_SIGNAL_RUN_LAST,
3709                   G_STRUCT_OFFSET (ClutterTextClass, cursor_event),
3710                   NULL, NULL,
3711                   _clutter_marshal_VOID__BOXED,
3712                   G_TYPE_NONE, 1,
3713                   CLUTTER_TYPE_GEOMETRY | G_SIGNAL_TYPE_STATIC_SCOPE);
3714
3715   /**
3716    * ClutterText::activate:
3717    * @self: the #ClutterText that emitted the signal
3718    *
3719    * The ::activate signal is emitted each time the actor is 'activated'
3720    * by the user, normally by pressing the 'Enter' key. The signal is
3721    * emitted only if #ClutterText:activatable is set to %TRUE.
3722    *
3723    * Since: 1.0
3724    */
3725   text_signals[ACTIVATE] =
3726     g_signal_new (I_("activate"),
3727                   G_TYPE_FROM_CLASS (gobject_class),
3728                   G_SIGNAL_RUN_LAST,
3729                   G_STRUCT_OFFSET (ClutterTextClass, activate),
3730                   NULL, NULL,
3731                   _clutter_marshal_VOID__VOID,
3732                   G_TYPE_NONE, 0);
3733
3734   binding_pool = clutter_binding_pool_get_for_class (klass);
3735
3736   clutter_text_add_move_binding (binding_pool, "move-left",
3737                                  CLUTTER_KEY_Left, CLUTTER_CONTROL_MASK,
3738                                  G_CALLBACK (clutter_text_real_move_left));
3739   clutter_text_add_move_binding (binding_pool, "move-left",
3740                                  CLUTTER_KEY_KP_Left, CLUTTER_CONTROL_MASK,
3741                                  G_CALLBACK (clutter_text_real_move_left));
3742   clutter_text_add_move_binding (binding_pool, "move-right",
3743                                  CLUTTER_KEY_Right, CLUTTER_CONTROL_MASK,
3744                                  G_CALLBACK (clutter_text_real_move_right));
3745   clutter_text_add_move_binding (binding_pool, "move-right",
3746                                  CLUTTER_KEY_KP_Right, CLUTTER_CONTROL_MASK,
3747                                  G_CALLBACK (clutter_text_real_move_right));
3748   clutter_text_add_move_binding (binding_pool, "move-up",
3749                                  CLUTTER_KEY_Up, 0,
3750                                  G_CALLBACK (clutter_text_real_move_up));
3751   clutter_text_add_move_binding (binding_pool, "move-up",
3752                                  CLUTTER_KEY_KP_Up, 0,
3753                                  G_CALLBACK (clutter_text_real_move_up));
3754   clutter_text_add_move_binding (binding_pool, "move-down",
3755                                  CLUTTER_KEY_Down, 0,
3756                                  G_CALLBACK (clutter_text_real_move_down));
3757   clutter_text_add_move_binding (binding_pool, "move-down",
3758                                  CLUTTER_KEY_KP_Down, 0,
3759                                  G_CALLBACK (clutter_text_real_move_down));
3760
3761   clutter_text_add_move_binding (binding_pool, "line-start",
3762                                  CLUTTER_KEY_Home, 0,
3763                                  G_CALLBACK (clutter_text_real_line_start));
3764   clutter_text_add_move_binding (binding_pool, "line-start",
3765                                  CLUTTER_KEY_KP_Home, 0,
3766                                  G_CALLBACK (clutter_text_real_line_start));
3767   clutter_text_add_move_binding (binding_pool, "line-start",
3768                                  CLUTTER_KEY_Begin, 0,
3769                                  G_CALLBACK (clutter_text_real_line_start));
3770   clutter_text_add_move_binding (binding_pool, "line-end",
3771                                  CLUTTER_KEY_End, 0,
3772                                  G_CALLBACK (clutter_text_real_line_end));
3773   clutter_text_add_move_binding (binding_pool, "line-end",
3774                                  CLUTTER_KEY_KP_End, 0,
3775                                  G_CALLBACK (clutter_text_real_line_end));
3776
3777   clutter_binding_pool_install_action (binding_pool, "select-all",
3778                                        CLUTTER_KEY_a, CLUTTER_CONTROL_MASK,
3779                                        G_CALLBACK (clutter_text_real_select_all),
3780                                        NULL, NULL);
3781
3782   clutter_binding_pool_install_action (binding_pool, "delete-next",
3783                                        CLUTTER_KEY_Delete, 0,
3784                                        G_CALLBACK (clutter_text_real_del_next),
3785                                        NULL, NULL);
3786   clutter_binding_pool_install_action (binding_pool, "delete-next",
3787                                        CLUTTER_KEY_Delete, CLUTTER_CONTROL_MASK,
3788                                        G_CALLBACK (clutter_text_real_del_word_next),
3789                                        NULL, NULL);
3790   clutter_binding_pool_install_action (binding_pool, "delete-next",
3791                                        CLUTTER_KEY_KP_Delete, 0,
3792                                        G_CALLBACK (clutter_text_real_del_next),
3793                                        NULL, NULL);
3794   clutter_binding_pool_install_action (binding_pool, "delete-next",
3795                                        CLUTTER_KEY_KP_Delete, CLUTTER_CONTROL_MASK,
3796                                        G_CALLBACK (clutter_text_real_del_word_next),
3797                                        NULL, NULL);
3798   clutter_binding_pool_install_action (binding_pool, "delete-prev",
3799                                        CLUTTER_KEY_BackSpace, 0,
3800                                        G_CALLBACK (clutter_text_real_del_prev),
3801                                        NULL, NULL);
3802   clutter_binding_pool_install_action (binding_pool, "delete-prev",
3803                                        CLUTTER_KEY_BackSpace, CLUTTER_SHIFT_MASK,
3804                                        G_CALLBACK (clutter_text_real_del_prev),
3805                                        NULL, NULL);
3806   clutter_binding_pool_install_action (binding_pool, "delete-prev",
3807                                        CLUTTER_KEY_BackSpace, CLUTTER_CONTROL_MASK,
3808                                        G_CALLBACK (clutter_text_real_del_word_prev),
3809                                        NULL, NULL);
3810
3811   clutter_binding_pool_install_action (binding_pool, "activate",
3812                                        CLUTTER_KEY_Return, 0,
3813                                        G_CALLBACK (clutter_text_real_activate),
3814                                        NULL, NULL);
3815   clutter_binding_pool_install_action (binding_pool, "activate",
3816                                        CLUTTER_KEY_KP_Enter, 0,
3817                                        G_CALLBACK (clutter_text_real_activate),
3818                                        NULL, NULL);
3819   clutter_binding_pool_install_action (binding_pool, "activate",
3820                                        CLUTTER_KEY_ISO_Enter, 0,
3821                                        G_CALLBACK (clutter_text_real_activate),
3822                                        NULL, NULL);
3823 }
3824
3825 static void
3826 clutter_text_init (ClutterText *self)
3827 {
3828   ClutterSettings *settings;
3829   ClutterTextPrivate *priv;
3830   gchar *font_name;
3831   int i, password_hint_time;
3832
3833   self->priv = priv = CLUTTER_TEXT_GET_PRIVATE (self);
3834
3835   priv->alignment     = PANGO_ALIGN_LEFT;
3836   priv->wrap          = FALSE;
3837   priv->wrap_mode     = PANGO_WRAP_WORD;
3838   priv->ellipsize     = PANGO_ELLIPSIZE_NONE;
3839   priv->use_underline = FALSE;
3840   priv->use_markup    = FALSE;
3841   priv->justify       = FALSE;
3842
3843   for (i = 0; i < N_CACHED_LAYOUTS; i++)
3844     priv->cached_layouts[i].layout = NULL;
3845
3846   /* default to "" so that clutter_text_get_text() will
3847    * return a valid string and we can safely call strlen()
3848    * or strcmp() on it
3849    */
3850   priv->buffer = NULL;
3851
3852   priv->text_color = default_text_color;
3853   priv->cursor_color = default_cursor_color;
3854   priv->selection_color = default_selection_color;
3855   priv->selected_text_color = default_selected_text_color;
3856
3857   /* get the default font name from the context; we don't use
3858    * set_font_description() here because we are initializing
3859    * the Text and we don't need notifications and sanity checks
3860    */
3861   settings = clutter_settings_get_default ();
3862   g_object_get (settings,
3863                 "font-name", &font_name,
3864                 "password-hint-time", &password_hint_time,
3865                 NULL);
3866
3867   priv->font_name = font_name; /* font_name is allocated */
3868   priv->font_desc = pango_font_description_from_string (font_name);
3869   priv->is_default_font = TRUE;
3870
3871   priv->position = -1;
3872   priv->selection_bound = -1;
3873
3874   priv->x_pos = -1;
3875   priv->cursor_visible = TRUE;
3876   priv->editable = FALSE;
3877   priv->selectable = TRUE;
3878
3879   priv->selection_color_set = FALSE;
3880   priv->cursor_color_set = FALSE;
3881   priv->selected_text_color_set = FALSE;
3882   priv->preedit_set = FALSE;
3883
3884   priv->password_char = 0;
3885   priv->show_password_hint = password_hint_time > 0;
3886   priv->password_hint_timeout = password_hint_time;
3887
3888   priv->text_y = 0;
3889
3890   priv->cursor_size = DEFAULT_CURSOR_SIZE;
3891   memset (&priv->cursor_pos, 0, sizeof (ClutterGeometry));
3892
3893   priv->settings_changed_id =
3894     g_signal_connect_swapped (clutter_get_default_backend (),
3895                               "settings-changed",
3896                               G_CALLBACK (clutter_text_settings_changed_cb),
3897                               self);
3898
3899   priv->direction_changed_id =
3900     g_signal_connect (self, "notify::text-direction",
3901                       G_CALLBACK (clutter_text_direction_changed_cb),
3902                       NULL);
3903 }
3904
3905 /**
3906  * clutter_text_new:
3907  *
3908  * Creates a new #ClutterText actor. This actor can be used to
3909  * display and edit text.
3910  *
3911  * Return value: the newly created #ClutterText actor
3912  *
3913  * Since: 1.0
3914  */
3915 ClutterActor *
3916 clutter_text_new (void)
3917 {
3918   return g_object_new (CLUTTER_TYPE_TEXT, NULL);
3919 }
3920
3921 /**
3922  * clutter_text_new_full:
3923  * @font_name: a string with a font description
3924  * @text: the contents of the actor
3925  * @color: the color to be used to render @text
3926  *
3927  * Creates a new #ClutterText actor, using @font_name as the font
3928  * description; @text will be used to set the contents of the actor;
3929  * and @color will be used as the color to render @text.
3930  *
3931  * This function is equivalent to calling clutter_text_new(),
3932  * clutter_text_set_font_name(), clutter_text_set_text() and
3933  * clutter_text_set_color().
3934  *
3935  * Return value: the newly created #ClutterText actor
3936  *
3937  * Since: 1.0
3938  */
3939 ClutterActor *
3940 clutter_text_new_full (const gchar        *font_name,
3941                        const gchar        *text,
3942                        const ClutterColor *color)
3943 {
3944   return g_object_new (CLUTTER_TYPE_TEXT,
3945                        "font-name", font_name,
3946                        "text", text,
3947                        "color", color,
3948                        NULL);
3949 }
3950
3951 /**
3952  * clutter_text_new_with_text:
3953  * @font_name: (allow-none): a string with a font description
3954  * @text: the contents of the actor
3955  *
3956  * Creates a new #ClutterText actor, using @font_name as the font
3957  * description; @text will be used to set the contents of the actor.
3958  *
3959  * This function is equivalent to calling clutter_text_new(),
3960  * clutter_text_set_font_name(), and clutter_text_set_text().
3961  *
3962  * Return value: the newly created #ClutterText actor
3963  *
3964  * Since: 1.0
3965  */
3966 ClutterActor *
3967 clutter_text_new_with_text (const gchar *font_name,
3968                             const gchar *text)
3969 {
3970   return g_object_new (CLUTTER_TYPE_TEXT,
3971                        "font-name", font_name,
3972                        "text", text,
3973                        NULL);
3974 }
3975
3976 static ClutterTextBuffer*
3977 get_buffer (ClutterText *self)
3978 {
3979   ClutterTextPrivate *priv = self->priv;
3980
3981   if (priv->buffer == NULL)
3982     {
3983       ClutterTextBuffer *buffer;
3984       buffer = clutter_text_buffer_new ();
3985       clutter_text_set_buffer (self, buffer);
3986       g_object_unref (buffer);
3987     }
3988
3989   return priv->buffer;
3990 }
3991
3992 /* GtkEntryBuffer signal handlers
3993  */
3994 static void
3995 buffer_inserted_text (ClutterTextBuffer *buffer,
3996                       guint              position,
3997                       const gchar       *chars,
3998                       guint              n_chars,
3999                       ClutterText       *self)
4000 {
4001   ClutterTextPrivate *priv;
4002   gint new_position;
4003   gint new_selection_bound;
4004   gsize n_bytes;
4005
4006   priv = self->priv;
4007   if (priv->position >= 0 || priv->selection_bound >= 0)
4008     {
4009       new_position = priv->position;
4010       new_selection_bound = priv->selection_bound;
4011
4012       if (position <= new_position)
4013         new_position += n_chars;
4014       if (position <= new_selection_bound)
4015         new_selection_bound += n_chars;
4016
4017       if (priv->position != new_position || priv->selection_bound != new_selection_bound)
4018         clutter_text_set_positions (self, new_position, new_selection_bound);
4019     }
4020
4021   n_bytes = g_utf8_offset_to_pointer (chars, n_chars) - chars;
4022   g_signal_emit (self, text_signals[INSERT_TEXT], 0, chars,
4023                  n_bytes, &position);
4024
4025   /* TODO: What are we supposed to with the out value of position? */
4026 }
4027
4028 static void
4029 buffer_deleted_text (ClutterTextBuffer *buffer,
4030                      guint              position,
4031                      guint              n_chars,
4032                      ClutterText       *self)
4033 {
4034   ClutterTextPrivate *priv;
4035   gint new_position;
4036   gint new_selection_bound;
4037
4038   priv = self->priv;
4039   if (priv->position >= 0 || priv->selection_bound >= 0)
4040     {
4041       new_position = priv->position;
4042       new_selection_bound = priv->selection_bound;
4043
4044       if (position < new_position)
4045         new_position -= n_chars;
4046       if (position < new_selection_bound)
4047         new_selection_bound -= n_chars;
4048
4049       if (priv->position != new_position || priv->selection_bound != new_selection_bound)
4050         clutter_text_set_positions (self, new_position, new_selection_bound);
4051     }
4052
4053   g_signal_emit (self, text_signals[DELETE_TEXT], 0, position, position + n_chars);
4054 }
4055
4056 static void
4057 buffer_notify_text (ClutterTextBuffer *buffer,
4058                     GParamSpec        *spec,
4059                     ClutterText       *self)
4060 {
4061   g_object_freeze_notify (G_OBJECT (self));
4062
4063   clutter_text_dirty_cache (self);
4064
4065   clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
4066
4067   g_signal_emit (self, text_signals[TEXT_CHANGED], 0);
4068   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_TEXT]);
4069
4070   g_object_thaw_notify (G_OBJECT (self));
4071 }
4072
4073 static void
4074 buffer_notify_max_length (ClutterTextBuffer *buffer,
4075                           GParamSpec        *spec,
4076                           ClutterText       *self)
4077 {
4078   g_object_notify (G_OBJECT (self), "max-length");
4079 }
4080
4081 static void
4082 buffer_connect_signals (ClutterText *self)
4083 {
4084   ClutterTextPrivate *priv = self->priv;
4085   g_signal_connect (priv->buffer, "inserted-text", G_CALLBACK (buffer_inserted_text), self);
4086   g_signal_connect (priv->buffer, "deleted-text", G_CALLBACK (buffer_deleted_text), self);
4087   g_signal_connect (priv->buffer, "notify::text", G_CALLBACK (buffer_notify_text), self);
4088   g_signal_connect (priv->buffer, "notify::max-length", G_CALLBACK (buffer_notify_max_length), self);
4089 }
4090
4091 static void
4092 buffer_disconnect_signals (ClutterText *self)
4093 {
4094   ClutterTextPrivate *priv = self->priv;
4095   g_signal_handlers_disconnect_by_func (priv->buffer, buffer_inserted_text, self);
4096   g_signal_handlers_disconnect_by_func (priv->buffer, buffer_deleted_text, self);
4097   g_signal_handlers_disconnect_by_func (priv->buffer, buffer_notify_text, self);
4098   g_signal_handlers_disconnect_by_func (priv->buffer, buffer_notify_max_length, self);
4099 }
4100
4101 /**
4102  * clutter_text_new_with_buffer:
4103  * @buffer: The buffer to use for the new #ClutterText.
4104  *
4105  * Creates a new entry with the specified text buffer.
4106  *
4107  * Return value: a new #ClutterText
4108  *
4109  * Since: 1.10
4110  */
4111 ClutterActor *
4112 clutter_text_new_with_buffer (ClutterTextBuffer *buffer)
4113 {
4114   g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), NULL);
4115   return g_object_new (CLUTTER_TYPE_TEXT, "buffer", buffer, NULL);
4116 }
4117
4118 /**
4119  * clutter_text_get_buffer:
4120  * @self: a #ClutterText
4121  *
4122  * Get the #ClutterTextBuffer object which holds the text for
4123  * this widget.
4124  *
4125  * Returns: (transfer none): A #GtkEntryBuffer object.
4126  *
4127  * Since: 1.10
4128  */
4129 ClutterTextBuffer*
4130 clutter_text_get_buffer (ClutterText *self)
4131 {
4132   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
4133
4134   return get_buffer (self);
4135 }
4136
4137 /**
4138  * clutter_text_set_buffer:
4139  * @self: a #ClutterText
4140  * @buffer: a #ClutterTextBuffer
4141  *
4142  * Set the #ClutterTextBuffer object which holds the text for
4143  * this widget.
4144  *
4145  * Since: 1.10
4146  */
4147 void
4148 clutter_text_set_buffer (ClutterText       *self,
4149                          ClutterTextBuffer *buffer)
4150 {
4151   ClutterTextPrivate *priv;
4152   GObject *obj;
4153
4154   g_return_if_fail (CLUTTER_IS_TEXT (self));
4155
4156   priv = self->priv;
4157
4158   if (buffer)
4159     {
4160       g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
4161       g_object_ref (buffer);
4162     }
4163
4164   if (priv->buffer)
4165     {
4166       buffer_disconnect_signals (self);
4167       g_object_unref (priv->buffer);
4168     }
4169
4170   priv->buffer = buffer;
4171
4172   if (priv->buffer)
4173      buffer_connect_signals (self);
4174
4175   obj = G_OBJECT (self);
4176   g_object_freeze_notify (obj);
4177   g_object_notify (obj, "buffer");
4178   g_object_notify (obj, "text");
4179   g_object_notify (obj, "max-length");
4180   g_object_thaw_notify (obj);
4181 }
4182
4183 /**
4184  * clutter_text_set_editable:
4185  * @self: a #ClutterText
4186  * @editable: whether the #ClutterText should be editable
4187  *
4188  * Sets whether the #ClutterText actor should be editable.
4189  *
4190  * An editable #ClutterText with key focus set using
4191  * clutter_actor_grab_key_focus() or clutter_stage_set_key_focus()
4192  * will receive key events and will update its contents accordingly.
4193  *
4194  * Since: 1.0
4195  */
4196 void
4197 clutter_text_set_editable (ClutterText *self,
4198                            gboolean     editable)
4199 {
4200   ClutterTextPrivate *priv;
4201
4202   g_return_if_fail (CLUTTER_IS_TEXT (self));
4203
4204   priv = self->priv;
4205
4206   if (priv->editable != editable)
4207     {
4208       priv->editable = editable;
4209
4210       clutter_text_queue_redraw (CLUTTER_ACTOR (self));
4211
4212       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_EDITABLE]);
4213     }
4214 }
4215
4216 /**
4217  * clutter_text_get_editable:
4218  * @self: a #ClutterText
4219  *
4220  * Retrieves whether a #ClutterText is editable or not.
4221  *
4222  * Return value: %TRUE if the actor is editable
4223  *
4224  * Since: 1.0
4225  */
4226 gboolean
4227 clutter_text_get_editable (ClutterText *self)
4228 {
4229   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
4230
4231   return self->priv->editable;
4232 }
4233
4234 /**
4235  * clutter_text_set_selectable:
4236  * @self: a #ClutterText
4237  * @selectable: whether the #ClutterText actor should be selectable
4238  *
4239  * Sets whether a #ClutterText actor should be selectable.
4240  *
4241  * A selectable #ClutterText will allow selecting its contents using
4242  * the pointer or the keyboard.
4243  *
4244  * Since: 1.0
4245  */
4246 void
4247 clutter_text_set_selectable (ClutterText *self,
4248                              gboolean     selectable)
4249 {
4250   ClutterTextPrivate *priv;
4251
4252   g_return_if_fail (CLUTTER_IS_TEXT (self));
4253
4254   priv = self->priv;
4255
4256   if (priv->selectable != selectable)
4257     {
4258       priv->selectable = selectable;
4259
4260       clutter_text_queue_redraw (CLUTTER_ACTOR (self));
4261
4262       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTABLE]);
4263     }
4264 }
4265
4266 /**
4267  * clutter_text_get_selectable:
4268  * @self: a #ClutterText
4269  *
4270  * Retrieves whether a #ClutterText is selectable or not.
4271  *
4272  * Return value: %TRUE if the actor is selectable
4273  *
4274  * Since: 1.0
4275  */
4276 gboolean
4277 clutter_text_get_selectable (ClutterText *self)
4278 {
4279   g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
4280
4281   return self->priv->selectable;
4282 }
4283
4284 /**
4285  * clutter_text_set_activatable:
4286  * @self: a #ClutterText
4287  * @activatable: whether the #ClutterText actor should be activatable
4288  *
4289  * Sets whether a #ClutterText actor should be activatable.
4290  *
4291  * An activatable #ClutterText actor will emit the #ClutterText::activate
4292  * signal whenever the 'Enter' (or 'Return') key is pressed; if it is not
4293  * activatable, a new line will be appended to the current content.
4294  *
4295  * An activatable #ClutterText must also be set as editable using
4296  * clutter_text_set_editable().
4297  *
4298  * Since: 1.0
4299  */
4300 void
4301 clutter_text_set_activatable (ClutterText *self,
4302                               gboolean     activatable)
4303 {
4304   ClutterTextPrivate *priv;
4305
4306   g_return_if_fail (CLUTTER_IS_TEXT (self));
4307
4308   priv = self->priv;
4309
4310   if (priv->activatable != activatable)
4311     {
4312       priv->activatable = activatable;
4313
4314       clutter_text_queue_redraw (CLUTTER_ACTOR (self));
4315
4316       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACTIVATABLE]);
4317     }
4318 }
4319
4320 /**
4321  * clutter_text_get_activatable:
4322  * @self: a #ClutterText
4323  *
4324  * Retrieves whether a #ClutterText is activatable or not.
4325  *
4326  * Return value: %TRUE if the actor is activatable
4327  *
4328  * Since: 1.0
4329  */
4330 gboolean
4331 clutter_text_get_activatable (ClutterText *self)
4332 {
4333   g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
4334
4335   return self->priv->activatable;
4336 }
4337
4338 /**
4339  * clutter_text_activate:
4340  * @self: a #ClutterText
4341  *
4342  * Emits the #ClutterText::activate signal, if @self has been set
4343  * as activatable using clutter_text_set_activatable().
4344  *
4345  * This function can be used to emit the ::activate signal inside
4346  * a #ClutterActor::captured-event or #ClutterActor::key-press-event
4347  * signal handlers before the default signal handler for the
4348  * #ClutterText is invoked.
4349  *
4350  * Return value: %TRUE if the ::activate signal has been emitted,
4351  *   and %FALSE otherwise
4352  *
4353  * Since: 1.0
4354  */
4355 gboolean
4356 clutter_text_activate (ClutterText *self)
4357 {
4358   ClutterTextPrivate *priv;
4359
4360   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
4361
4362   priv = self->priv;
4363
4364   if (priv->activatable)
4365     {
4366       g_signal_emit (self, text_signals[ACTIVATE], 0);
4367       return TRUE;
4368     }
4369
4370   return FALSE;
4371 }
4372
4373 /**
4374  * clutter_text_set_cursor_visible:
4375  * @self: a #ClutterText
4376  * @cursor_visible: whether the cursor should be visible
4377  *
4378  * Sets whether the cursor of a #ClutterText actor should be
4379  * visible or not.
4380  *
4381  * The color of the cursor will be the same as the text color
4382  * unless clutter_text_set_cursor_color() has been called.
4383  *
4384  * The size of the cursor can be set using clutter_text_set_cursor_size().
4385  *
4386  * The position of the cursor can be changed programmatically using
4387  * clutter_text_set_cursor_position().
4388  *
4389  * Since: 1.0
4390  */
4391 void
4392 clutter_text_set_cursor_visible (ClutterText *self,
4393                                  gboolean     cursor_visible)
4394 {
4395   ClutterTextPrivate *priv;
4396
4397   g_return_if_fail (CLUTTER_IS_TEXT (self));
4398
4399   priv = self->priv;
4400
4401   if (priv->cursor_visible != cursor_visible)
4402     {
4403       priv->cursor_visible = cursor_visible;
4404
4405       clutter_text_queue_redraw (CLUTTER_ACTOR (self));
4406
4407       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_VISIBLE]);
4408     }
4409 }
4410
4411 /**
4412  * clutter_text_get_cursor_visible:
4413  * @self: a #ClutterText
4414  *
4415  * Retrieves whether the cursor of a #ClutterText actor is visible.
4416  *
4417  * Return value: %TRUE if the cursor is visible
4418  *
4419  * Since: 1.0
4420  */
4421 gboolean
4422 clutter_text_get_cursor_visible (ClutterText *self)
4423 {
4424   g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
4425
4426   return self->priv->cursor_visible;
4427 }
4428
4429 /**
4430  * clutter_text_set_cursor_color:
4431  * @self: a #ClutterText
4432  * @color: (allow-none): the color of the cursor, or %NULL to unset it
4433  *
4434  * Sets the color of the cursor of a #ClutterText actor.
4435  *
4436  * If @color is %NULL, the cursor color will be the same as the
4437  * text color.
4438  *
4439  * Since: 1.0
4440  */
4441 void
4442 clutter_text_set_cursor_color (ClutterText        *self,
4443                                const ClutterColor *color)
4444 {
4445   g_return_if_fail (CLUTTER_IS_TEXT (self));
4446
4447   clutter_text_set_color_animated (self, obj_props[PROP_CURSOR_COLOR], color);
4448 }
4449
4450 /**
4451  * clutter_text_get_cursor_color:
4452  * @self: a #ClutterText
4453  * @color: (out): return location for a #ClutterColor
4454  *
4455  * Retrieves the color of the cursor of a #ClutterText actor.
4456  *
4457  * Since: 1.0
4458  */
4459 void
4460 clutter_text_get_cursor_color (ClutterText  *self,
4461                                ClutterColor *color)
4462 {
4463   ClutterTextPrivate *priv;
4464
4465   g_return_if_fail (CLUTTER_IS_TEXT (self));
4466   g_return_if_fail (color != NULL);
4467
4468   priv = self->priv;
4469
4470   *color = priv->cursor_color;
4471 }
4472
4473 /**
4474  * clutter_text_set_selection:
4475  * @self: a #ClutterText
4476  * @start_pos: start of the selection, in characters
4477  * @end_pos: end of the selection, in characters
4478  *
4479  * Selects the region of text between @start_pos and @end_pos.
4480  *
4481  * This function changes the position of the cursor to match
4482  * @start_pos and the selection bound to match @end_pos.
4483  *
4484  * Since: 1.0
4485  */
4486 void
4487 clutter_text_set_selection (ClutterText *self,
4488                             gssize       start_pos,
4489                             gssize       end_pos)
4490 {
4491   guint n_chars;
4492
4493   g_return_if_fail (CLUTTER_IS_TEXT (self));
4494
4495   n_chars = clutter_text_buffer_get_length (get_buffer (self));
4496   if (end_pos < 0)
4497     end_pos = n_chars;
4498
4499   start_pos = MIN (n_chars, start_pos);
4500   end_pos = MIN (n_chars, end_pos);
4501
4502   clutter_text_set_positions (self, start_pos, end_pos);
4503 }
4504
4505 /**
4506  * clutter_text_get_selection:
4507  * @self: a #ClutterText
4508  *
4509  * Retrieves the currently selected text.
4510  *
4511  * Return value: a newly allocated string containing the currently
4512  *   selected text, or %NULL. Use g_free() to free the returned
4513  *   string.
4514  *
4515  * Since: 1.0
4516  */
4517 gchar *
4518 clutter_text_get_selection (ClutterText *self)
4519 {
4520   ClutterTextPrivate *priv;
4521   gchar *str;
4522   gint len;
4523   gint start_index, end_index;
4524   gint start_offset, end_offset;
4525   const gchar *text;
4526
4527   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
4528
4529   priv = self->priv;
4530
4531   start_index = priv->position;
4532   end_index = priv->selection_bound;
4533
4534   if (end_index == start_index)
4535     return g_strdup ("");
4536
4537   if ((end_index != -1 && end_index < start_index) ||
4538       start_index == -1)
4539     {
4540       gint temp = start_index;
4541       start_index = end_index;
4542       end_index = temp;
4543     }
4544
4545   text = clutter_text_buffer_get_text (get_buffer (self));
4546   start_offset = offset_to_bytes (text, start_index);
4547   end_offset = offset_to_bytes (text, end_index);
4548   len = end_offset - start_offset;
4549
4550   str = g_malloc (len + 1);
4551   g_utf8_strncpy (str, text + start_offset, end_index - start_index);
4552
4553   return str;
4554 }
4555
4556 /**
4557  * clutter_text_set_selection_bound:
4558  * @self: a #ClutterText
4559  * @selection_bound: the position of the end of the selection, in characters
4560  *
4561  * Sets the other end of the selection, starting from the current
4562  * cursor position.
4563  *
4564  * If @selection_bound is -1, the selection unset.
4565  *
4566  * Since: 1.0
4567  */
4568 void
4569 clutter_text_set_selection_bound (ClutterText *self,
4570                                   gint         selection_bound)
4571 {
4572   ClutterTextPrivate *priv;
4573
4574   g_return_if_fail (CLUTTER_IS_TEXT (self));
4575
4576   priv = self->priv;
4577
4578   if (priv->selection_bound != selection_bound)
4579     {
4580       gint len = clutter_text_buffer_get_length (get_buffer (self));;
4581
4582       if (selection_bound < 0 || selection_bound >= len)
4583         priv->selection_bound = -1;
4584       else
4585         priv->selection_bound = selection_bound;
4586
4587       clutter_text_queue_redraw (CLUTTER_ACTOR (self));
4588
4589       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
4590     }
4591 }
4592
4593 /**
4594  * clutter_text_get_selection_bound:
4595  * @self: a #ClutterText
4596  *
4597  * Retrieves the other end of the selection of a #ClutterText actor,
4598  * in characters from the current cursor position.
4599  *
4600  * Return value: the position of the other end of the selection
4601  *
4602  * Since: 1.0
4603  */
4604 gint
4605 clutter_text_get_selection_bound (ClutterText *self)
4606 {
4607   g_return_val_if_fail (CLUTTER_IS_TEXT (self), -1);
4608
4609   return self->priv->selection_bound;
4610 }
4611
4612 /**
4613  * clutter_text_set_selection_color:
4614  * @self: a #ClutterText
4615  * @color: (allow-none): the color of the selection, or %NULL to unset it
4616  *
4617  * Sets the color of the selection of a #ClutterText actor.
4618  *
4619  * If @color is %NULL, the selection color will be the same as the
4620  * cursor color, or if no cursor color is set either then it will be
4621  * the same as the text color.
4622  *
4623  * Since: 1.0
4624  */
4625 void
4626 clutter_text_set_selection_color (ClutterText        *self,
4627                                   const ClutterColor *color)
4628 {
4629   g_return_if_fail (CLUTTER_IS_TEXT (self));
4630
4631   clutter_text_set_color_animated (self, obj_props[PROP_SELECTION_COLOR],
4632                                    color);
4633 }
4634
4635 /**
4636  * clutter_text_get_selection_color:
4637  * @self: a #ClutterText
4638  * @color: (out caller-allocates): return location for a #ClutterColor
4639  *
4640  * Retrieves the color of the selection of a #ClutterText actor.
4641  *
4642  * Since: 1.0
4643  */
4644 void
4645 clutter_text_get_selection_color (ClutterText  *self,
4646                                   ClutterColor *color)
4647 {
4648   ClutterTextPrivate *priv;
4649
4650   g_return_if_fail (CLUTTER_IS_TEXT (self));
4651   g_return_if_fail (color != NULL);
4652
4653   priv = self->priv;
4654
4655   *color = priv->selection_color;
4656 }
4657
4658 /**
4659  * clutter_text_set_selected_text_color:
4660  * @self: a #ClutterText
4661  * @color: (allow-none): the selected text color, or %NULL to unset it
4662  *
4663  * Sets the selected text color of a #ClutterText actor.
4664  *
4665  * If @color is %NULL, the selected text color will be the same as the
4666  * selection color, which then falls back to cursor, and then text color.
4667  *
4668  * Since: 1.8
4669  */
4670 void
4671 clutter_text_set_selected_text_color (ClutterText        *self,
4672                                       const ClutterColor *color)
4673 {
4674   g_return_if_fail (CLUTTER_IS_TEXT (self));
4675
4676   clutter_text_set_color_animated (self, obj_props[PROP_SELECTED_TEXT_COLOR],
4677                                    color);
4678 }
4679
4680 /**
4681  * clutter_text_get_selected_text_color:
4682  * @self: a #ClutterText
4683  * @color: (out caller-allocates): return location for a #ClutterColor
4684  *
4685  * Retrieves the color of selected text of a #ClutterText actor.
4686  *
4687  * Since: 1.8
4688  */
4689 void
4690 clutter_text_get_selected_text_color (ClutterText  *self,
4691                                       ClutterColor *color)
4692 {
4693   ClutterTextPrivate *priv;
4694
4695   g_return_if_fail (CLUTTER_IS_TEXT (self));
4696   g_return_if_fail (color != NULL);
4697
4698   priv = self->priv;
4699
4700   *color = priv->selected_text_color;
4701 }
4702
4703 /**
4704  * clutter_text_set_font_description:
4705  * @self: a #ClutterText
4706  * @font_desc: a #PangoFontDescription
4707  *
4708  * Sets @font_desc as the font description for a #ClutterText
4709  *
4710  * The #PangoFontDescription is copied by the #ClutterText actor
4711  * so you can safely call pango_font_description_free() on it after
4712  * calling this function.
4713  *
4714  * Since: 1.2
4715  */
4716 void
4717 clutter_text_set_font_description (ClutterText          *self,
4718                                    PangoFontDescription *font_desc)
4719 {
4720   PangoFontDescription *copy;
4721
4722   g_return_if_fail (CLUTTER_IS_TEXT (self));
4723
4724   copy = pango_font_description_copy (font_desc);
4725   clutter_text_set_font_description_internal (self, copy);
4726 }
4727
4728 /**
4729  * clutter_text_get_font_description:
4730  * @self: a #ClutterText
4731  *
4732  * Retrieves the #PangoFontDescription used by @self
4733  *
4734  * Return value: a #PangoFontDescription. The returned value is owned
4735  *   by the #ClutterText actor and it should not be modified or freed
4736  *
4737  * Since: 1.2
4738  */
4739 PangoFontDescription *
4740 clutter_text_get_font_description (ClutterText *self)
4741 {
4742   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
4743
4744   return self->priv->font_desc;
4745 }
4746
4747 /**
4748  * clutter_text_get_font_name:
4749  * @self: a #ClutterText
4750  *
4751  * Retrieves the font name as set by clutter_text_set_font_name().
4752  *
4753  * Return value: a string containing the font name. The returned
4754  *   string is owned by the #ClutterText actor and should not be
4755  *   modified or freed
4756  *
4757  * Since: 1.0
4758  */
4759 const gchar *
4760 clutter_text_get_font_name (ClutterText *text)
4761 {
4762   g_return_val_if_fail (CLUTTER_IS_TEXT (text), NULL);
4763
4764   return text->priv->font_name;
4765 }
4766
4767 /**
4768  * clutter_text_set_font_name:
4769  * @self: a #ClutterText
4770  * @font_name: (allow-none): a font name, or %NULL to set the default font name
4771  *
4772  * Sets the font used by a #ClutterText. The @font_name string
4773  * must either be %NULL, which means that the font name from the
4774  * default #ClutterBackend will be used; or be something that can
4775  * be parsed by the pango_font_description_from_string() function,
4776  * like:
4777  *
4778  * |[
4779  *   clutter_text_set_font_name (text, "Sans 10pt");
4780  *   clutter_text_set_font_name (text, "Serif 16px");
4781  *   clutter_text_set_font_name (text, "Helvetica 10");
4782  * ]|
4783  *
4784  * Since: 1.0
4785  */
4786 void
4787 clutter_text_set_font_name (ClutterText *self,
4788                             const gchar *font_name)
4789 {
4790   ClutterTextPrivate *priv;
4791   PangoFontDescription *desc;
4792   gboolean is_default_font;
4793
4794   g_return_if_fail (CLUTTER_IS_TEXT (self));
4795
4796   /* get the default font name from the backend */
4797   if (font_name == NULL || font_name[0] == '\0')
4798     {
4799       ClutterSettings *settings = clutter_settings_get_default ();
4800       gchar *default_font_name = NULL;
4801
4802       g_object_get (settings, "font-name", &default_font_name, NULL);
4803
4804       if (default_font_name != NULL)
4805         font_name = default_font_name;
4806       else
4807         {
4808           /* last fallback */
4809           default_font_name = g_strdup ("Sans 12");
4810         }
4811
4812       is_default_font = TRUE;
4813     }
4814   else
4815     is_default_font = FALSE;
4816
4817   priv = self->priv;
4818
4819   if (g_strcmp0 (priv->font_name, font_name) == 0)
4820     goto out;
4821
4822   desc = pango_font_description_from_string (font_name);
4823   if (!desc)
4824     {
4825       g_warning ("Attempting to create a PangoFontDescription for "
4826                  "font name '%s', but failed.",
4827                  font_name);
4828       goto out;
4829     }
4830
4831   /* this will set the font_name field as well */
4832   clutter_text_set_font_description_internal (self, desc);
4833   priv->is_default_font = is_default_font;
4834
4835   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_FONT_NAME]);
4836
4837 out:
4838   if (is_default_font)
4839     g_free ((gchar *) font_name);
4840 }
4841
4842 /**
4843  * clutter_text_get_text:
4844  * @self: a #ClutterText
4845  *
4846  * Retrieves a pointer to the current contents of a #ClutterText
4847  * actor.
4848  *
4849  * If you need a copy of the contents for manipulating, either
4850  * use g_strdup() on the returned string, or use:
4851  *
4852  * |[
4853  *    copy = clutter_text_get_chars (text, 0, -1);
4854  * ]|
4855  *
4856  * Which will return a newly allocated string.
4857  *
4858  * If the #ClutterText actor is empty, this function will return
4859  * an empty string, and not %NULL.
4860  *
4861  * Return value: (transfer none): the contents of the actor. The returned
4862  *   string is owned by the #ClutterText actor and should never be modified
4863  *   or freed
4864  *
4865  * Since: 1.0
4866  */
4867 const gchar *
4868 clutter_text_get_text (ClutterText *self)
4869 {
4870   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
4871
4872   return clutter_text_buffer_get_text (get_buffer (self));
4873 }
4874
4875 static inline void
4876 clutter_text_set_use_markup_internal (ClutterText *self,
4877                                       gboolean     use_markup)
4878 {
4879   ClutterTextPrivate *priv = self->priv;
4880
4881   if (priv->use_markup != use_markup)
4882     {
4883       priv->use_markup = use_markup;
4884
4885       /* reset the attributes lists so that they can be
4886        * re-generated
4887        */
4888       if (priv->effective_attrs != NULL)
4889         {
4890           pango_attr_list_unref (priv->effective_attrs);
4891           priv->effective_attrs = NULL;
4892         }
4893
4894       if (priv->markup_attrs)
4895         {
4896           pango_attr_list_unref (priv->markup_attrs);
4897           priv->markup_attrs = NULL;
4898         }
4899
4900       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_USE_MARKUP]);
4901     }
4902 }
4903
4904 /**
4905  * clutter_text_set_text:
4906  * @self: a #ClutterText
4907  * @text: (allow-none): the text to set. Passing %NULL is the same
4908  *   as passing "" (the empty string)
4909  *
4910  * Sets the contents of a #ClutterText actor.
4911  *
4912  * If the #ClutterText:use-markup property was set to %TRUE it
4913  * will be reset to %FALSE as a side effect. If you want to
4914  * maintain the #ClutterText:use-markup you should use the
4915  * clutter_text_set_markup() function instead
4916  *
4917  * Since: 1.0
4918  */
4919 void
4920 clutter_text_set_text (ClutterText *self,
4921                        const gchar *text)
4922 {
4923   g_return_if_fail (CLUTTER_IS_TEXT (self));
4924
4925   /* if the text is editable (i.e. there is not markup flag to reset) then
4926    * changing the contents will result in selection and cursor changes that
4927    * we should avoid
4928    */
4929   if (self->priv->editable)
4930     {
4931       if (g_strcmp0 (clutter_text_buffer_get_text (get_buffer (self)), text) == 0)
4932         return;
4933     }
4934
4935   clutter_text_set_use_markup_internal (self, FALSE);
4936   clutter_text_buffer_set_text (get_buffer (self), text ? text : "", -1);
4937 }
4938
4939 /**
4940  * clutter_text_set_markup:
4941  * @self: a #ClutterText
4942  * @markup: (allow-none): a string containing Pango markup.
4943  *   Passing %NULL is the same as passing "" (the empty string)
4944  *
4945  * Sets @markup as the contents of a #ClutterText.
4946  *
4947  * This is a convenience function for setting a string containing
4948  * Pango markup, and it is logically equivalent to:
4949  *
4950  * |[
4951  *   /&ast; the order is important &ast;/
4952  *   clutter_text_set_text (CLUTTER_TEXT (actor), markup);
4953  *   clutter_text_set_use_markup (CLUTTER_TEXT (actor), TRUE);
4954  * ]|
4955  *
4956  * Since: 1.0
4957  */
4958 void
4959 clutter_text_set_markup (ClutterText *self,
4960                          const gchar *markup)
4961 {
4962   g_return_if_fail (CLUTTER_IS_TEXT (self));
4963
4964   clutter_text_set_use_markup_internal (self, TRUE);
4965   if (markup != NULL && *markup != '\0')
4966     clutter_text_set_markup_internal (self, markup);
4967   else
4968     clutter_text_buffer_set_text (get_buffer (self), "", 0);
4969 }
4970
4971 /**
4972  * clutter_text_get_layout:
4973  * @self: a #ClutterText
4974  *
4975  * Retrieves the current #PangoLayout used by a #ClutterText actor.
4976  *
4977  * Return value: (transfer none): a #PangoLayout. The returned object is owned by
4978  *   the #ClutterText actor and should not be modified or freed
4979  *
4980  * Since: 1.0
4981  */
4982 PangoLayout *
4983 clutter_text_get_layout (ClutterText *self)
4984 {
4985   gfloat width, height;
4986
4987   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
4988
4989   if (self->priv->editable && self->priv->single_line_mode)
4990     return clutter_text_create_layout (self, -1, -1);
4991
4992   clutter_actor_get_size (CLUTTER_ACTOR (self), &width, &height);
4993
4994   return clutter_text_create_layout (self, width, height);
4995 }
4996
4997 /**
4998  * clutter_text_set_color:
4999  * @self: a #ClutterText
5000  * @color: a #ClutterColor
5001  *
5002  * Sets the color of the contents of a #ClutterText actor.
5003  *
5004  * The overall opacity of the #ClutterText actor will be the
5005  * result of the alpha value of @color and the composited
5006  * opacity of the actor itself on the scenegraph, as returned
5007  * by clutter_actor_get_paint_opacity().
5008  *
5009  * Since: 1.0
5010  */
5011 void
5012 clutter_text_set_color (ClutterText        *self,
5013                         const ClutterColor *color)
5014 {
5015   g_return_if_fail (CLUTTER_IS_TEXT (self));
5016   g_return_if_fail (color != NULL);
5017
5018   clutter_text_set_color_animated (self, obj_props[PROP_COLOR], color);
5019 }
5020
5021 /**
5022  * clutter_text_get_color:
5023  * @self: a #ClutterText
5024  * @color: (out caller-allocates): return location for a #ClutterColor
5025  *
5026  * Retrieves the text color as set by clutter_text_set_color().
5027  *
5028  * Since: 1.0
5029  */
5030 void
5031 clutter_text_get_color (ClutterText  *self,
5032                         ClutterColor *color)
5033 {
5034   ClutterTextPrivate *priv;
5035
5036   g_return_if_fail (CLUTTER_IS_TEXT (self));
5037   g_return_if_fail (color != NULL);
5038
5039   priv = self->priv;
5040
5041   *color = priv->text_color;
5042 }
5043
5044 /**
5045  * clutter_text_set_ellipsize:
5046  * @self: a #ClutterText
5047  * @mode: a #PangoEllipsizeMode
5048  *
5049  * Sets the mode used to ellipsize (add an ellipsis: "...") to the
5050  * text if there is not enough space to render the entire contents
5051  * of a #ClutterText actor
5052  *
5053  * Since: 1.0
5054  */
5055 void
5056 clutter_text_set_ellipsize (ClutterText        *self,
5057                             PangoEllipsizeMode  mode)
5058 {
5059   ClutterTextPrivate *priv;
5060
5061   g_return_if_fail (CLUTTER_IS_TEXT (self));
5062   g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE &&
5063                     mode <= PANGO_ELLIPSIZE_END);
5064
5065   priv = self->priv;
5066
5067   if ((PangoEllipsizeMode) priv->ellipsize != mode)
5068     {
5069       priv->ellipsize = mode;
5070
5071       clutter_text_dirty_cache (self);
5072
5073       clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5074
5075       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ELLIPSIZE]);
5076     }
5077 }
5078
5079 /**
5080  * clutter_text_get_ellipsize:
5081  * @self: a #ClutterText
5082  *
5083  * Returns the ellipsizing position of a #ClutterText actor, as
5084  * set by clutter_text_set_ellipsize().
5085  *
5086  * Return value: #PangoEllipsizeMode
5087  *
5088  * Since: 1.0
5089  */
5090 PangoEllipsizeMode
5091 clutter_text_get_ellipsize (ClutterText *self)
5092 {
5093   g_return_val_if_fail (CLUTTER_IS_TEXT (self), PANGO_ELLIPSIZE_NONE);
5094
5095   return self->priv->ellipsize;
5096 }
5097
5098 /**
5099  * clutter_text_get_line_wrap:
5100  * @self: a #ClutterText
5101  *
5102  * Retrieves the value set using clutter_text_set_line_wrap().
5103  *
5104  * Return value: %TRUE if the #ClutterText actor should wrap
5105  *   its contents
5106  *
5107  * Since: 1.0
5108  */
5109 gboolean
5110 clutter_text_get_line_wrap (ClutterText *self)
5111 {
5112   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
5113
5114   return self->priv->wrap;
5115 }
5116
5117 /**
5118  * clutter_text_set_line_wrap:
5119  * @self: a #ClutterText
5120  * @line_wrap: whether the contents should wrap
5121  *
5122  * Sets whether the contents of a #ClutterText actor should wrap,
5123  * if they don't fit the size assigned to the actor.
5124  *
5125  * Since: 1.0
5126  */
5127 void
5128 clutter_text_set_line_wrap (ClutterText *self,
5129                             gboolean     line_wrap)
5130 {
5131   ClutterTextPrivate *priv;
5132
5133   g_return_if_fail (CLUTTER_IS_TEXT (self));
5134
5135   priv = self->priv;
5136
5137   if (priv->wrap != line_wrap)
5138     {
5139       priv->wrap = line_wrap;
5140
5141       clutter_text_dirty_cache (self);
5142
5143       clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5144
5145       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_LINE_WRAP]);
5146     }
5147 }
5148
5149 /**
5150  * clutter_text_set_line_wrap_mode:
5151  * @self: a #ClutterText
5152  * @wrap_mode: the line wrapping mode
5153  *
5154  * If line wrapping is enabled (see clutter_text_set_line_wrap()) this
5155  * function controls how the line wrapping is performed. The default is
5156  * %PANGO_WRAP_WORD which means wrap on word boundaries.
5157  *
5158  * Since: 1.0
5159  */
5160 void
5161 clutter_text_set_line_wrap_mode (ClutterText   *self,
5162                                  PangoWrapMode  wrap_mode)
5163 {
5164   ClutterTextPrivate *priv;
5165
5166   g_return_if_fail (CLUTTER_IS_TEXT (self));
5167
5168   priv = self->priv;
5169
5170   if (priv->wrap_mode != wrap_mode)
5171     {
5172       priv->wrap_mode = wrap_mode;
5173
5174       clutter_text_dirty_cache (self);
5175
5176       clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5177
5178       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_LINE_WRAP_MODE]);
5179     }
5180 }
5181
5182 /**
5183  * clutter_text_get_line_wrap_mode:
5184  * @self: a #ClutterText
5185  *
5186  * Retrieves the line wrap mode used by the #ClutterText actor.
5187  *
5188  * See clutter_text_set_line_wrap_mode ().
5189  *
5190  * Return value: the wrap mode used by the #ClutterText
5191  *
5192  * Since: 1.0
5193  */
5194 PangoWrapMode
5195 clutter_text_get_line_wrap_mode (ClutterText *self)
5196 {
5197   g_return_val_if_fail (CLUTTER_IS_TEXT (self), PANGO_WRAP_WORD);
5198
5199   return self->priv->wrap_mode;
5200 }
5201
5202 /**
5203  * clutter_text_set_attributes:
5204  * @self: a #ClutterText
5205  * @attrs: (allow-none): a #PangoAttrList or %NULL to unset the attributes
5206  *
5207  * Sets the attributes list that are going to be applied to the
5208  * #ClutterText contents.
5209  *
5210  * The #ClutterText actor will take a reference on the #PangoAttrList
5211  * passed to this function.
5212  *
5213  * Since: 1.0
5214  */
5215 void
5216 clutter_text_set_attributes (ClutterText   *self,
5217                              PangoAttrList *attrs)
5218 {
5219   ClutterTextPrivate *priv;
5220
5221   g_return_if_fail (CLUTTER_IS_TEXT (self));
5222
5223   priv = self->priv;
5224
5225   if (attrs)
5226     pango_attr_list_ref (attrs);
5227
5228   if (priv->attrs)
5229     pango_attr_list_unref (priv->attrs);
5230
5231   priv->attrs = attrs;
5232
5233   /* Clear the effective attributes so they will be regenerated when a
5234      layout is created */
5235   if (priv->effective_attrs)
5236     {
5237       pango_attr_list_unref (priv->effective_attrs);
5238       priv->effective_attrs = NULL;
5239     }
5240
5241   clutter_text_dirty_cache (self);
5242
5243   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ATTRIBUTES]);
5244
5245   clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5246 }
5247
5248 /**
5249  * clutter_text_get_attributes:
5250  * @self: a #ClutterText
5251  *
5252  * Gets the attribute list that was set on the #ClutterText actor
5253  * clutter_text_set_attributes(), if any.
5254  *
5255  * Return value: (transfer none): the attribute list, or %NULL if none was set. The
5256  *  returned value is owned by the #ClutterText and should not be unreferenced.
5257  *
5258  * Since: 1.0
5259  */
5260 PangoAttrList *
5261 clutter_text_get_attributes (ClutterText *self)
5262 {
5263   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
5264
5265   return self->priv->attrs;
5266 }
5267
5268 /**
5269  * clutter_text_set_line_alignment:
5270  * @self: a #ClutterText
5271  * @alignment: A #PangoAlignment
5272  *
5273  * Sets the way that the lines of a wrapped label are aligned with
5274  * respect to each other. This does not affect the overall alignment
5275  * of the label within its allocated or specified width.
5276  *
5277  * To align a #ClutterText actor you should add it to a container
5278  * that supports alignment, or use the anchor point.
5279  *
5280  * Since: 1.0
5281  */
5282 void
5283 clutter_text_set_line_alignment (ClutterText    *self,
5284                                  PangoAlignment  alignment)
5285 {
5286   ClutterTextPrivate *priv;
5287
5288   g_return_if_fail (CLUTTER_IS_TEXT (self));
5289
5290   priv = self->priv;
5291
5292   if (priv->alignment != alignment)
5293     {
5294       priv->alignment = alignment;
5295
5296       clutter_text_dirty_cache (self);
5297
5298       clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5299
5300       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_LINE_ALIGNMENT]);
5301     }
5302 }
5303
5304 /**
5305  * clutter_text_get_line_alignment:
5306  * @self: a #ClutterText
5307  *
5308  * Retrieves the alignment of a #ClutterText, as set by
5309  * clutter_text_set_line_alignment().
5310  *
5311  * Return value: a #PangoAlignment
5312  *
5313  * Since: 1.0
5314  */
5315 PangoAlignment
5316 clutter_text_get_line_alignment (ClutterText *self)
5317 {
5318   g_return_val_if_fail (CLUTTER_IS_TEXT (self), PANGO_ALIGN_LEFT);
5319
5320   return self->priv->alignment;
5321 }
5322
5323 /**
5324  * clutter_text_set_use_markup:
5325  * @self: a #ClutterText
5326  * @setting: %TRUE if the text should be parsed for markup.
5327  *
5328  * Sets whether the contents of the #ClutterText actor contains markup
5329  * in <link linkend="PangoMarkupFormat">Pango's text markup language</link>.
5330  *
5331  * Setting #ClutterText:use-markup on an editable #ClutterText will
5332  * not have any effect except hiding the markup.
5333  *
5334  * See also #ClutterText:use-markup.
5335  *
5336  * Since: 1.0
5337  */
5338 void
5339 clutter_text_set_use_markup (ClutterText *self,
5340                              gboolean     setting)
5341 {
5342   const gchar *text;
5343
5344   g_return_if_fail (CLUTTER_IS_TEXT (self));
5345
5346   text = clutter_text_buffer_get_text (get_buffer (self));
5347
5348   clutter_text_set_use_markup_internal (self, setting);
5349
5350   if (setting)
5351     clutter_text_set_markup_internal (self, text);
5352
5353   clutter_text_dirty_cache (self);
5354
5355   clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5356 }
5357
5358 /**
5359  * clutter_text_get_use_markup:
5360  * @self: a #ClutterText
5361  *
5362  * Retrieves whether the contents of the #ClutterText actor should be
5363  * parsed for the Pango text markup.
5364  *
5365  * Return value: %TRUE if the contents will be parsed for markup
5366  *
5367  * Since: 1.0
5368  */
5369 gboolean
5370 clutter_text_get_use_markup (ClutterText *self)
5371 {
5372   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
5373
5374   return self->priv->use_markup;
5375 }
5376
5377 /**
5378  * clutter_text_set_justify:
5379  * @self: a #ClutterText
5380  * @justify: whether the text should be justified
5381  *
5382  * Sets whether the text of the #ClutterText actor should be justified
5383  * on both margins. This setting is ignored if Clutter is compiled
5384  * against Pango &lt; 1.18.
5385  *
5386  * Since: 1.0
5387  */
5388 void
5389 clutter_text_set_justify (ClutterText *self,
5390                           gboolean     justify)
5391 {
5392   ClutterTextPrivate *priv;
5393
5394   g_return_if_fail (CLUTTER_IS_TEXT (self));
5395
5396   priv = self->priv;
5397
5398   if (priv->justify != justify)
5399     {
5400       priv->justify = justify;
5401
5402       clutter_text_dirty_cache (self);
5403
5404       clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5405
5406       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_JUSTIFY]);
5407     }
5408 }
5409
5410 /**
5411  * clutter_text_get_justify:
5412  * @self: a #ClutterText
5413  *
5414  * Retrieves whether the #ClutterText actor should justify its contents
5415  * on both margins.
5416  *
5417  * Return value: %TRUE if the text should be justified
5418  *
5419  * Since: 0.6
5420  */
5421 gboolean
5422 clutter_text_get_justify (ClutterText *self)
5423 {
5424   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
5425
5426   return self->priv->justify;
5427 }
5428
5429 /**
5430  * clutter_text_get_cursor_position:
5431  * @self: a #ClutterText
5432  *
5433  * Retrieves the cursor position.
5434  *
5435  * Return value: the cursor position, in characters
5436  *
5437  * Since: 1.0
5438  */
5439 gint
5440 clutter_text_get_cursor_position (ClutterText *self)
5441 {
5442   g_return_val_if_fail (CLUTTER_IS_TEXT (self), -1);
5443
5444   return self->priv->position;
5445 }
5446
5447 /**
5448  * clutter_text_set_cursor_position:
5449  * @self: a #ClutterText
5450  * @position: the new cursor position, in characters
5451  *
5452  * Sets the cursor of a #ClutterText actor at @position.
5453  *
5454  * The position is expressed in characters, not in bytes.
5455  *
5456  * Since: 1.0
5457  */
5458 void
5459 clutter_text_set_cursor_position (ClutterText *self,
5460                                   gint         position)
5461 {
5462   ClutterTextPrivate *priv;
5463   gint len;
5464
5465   g_return_if_fail (CLUTTER_IS_TEXT (self));
5466
5467   priv = self->priv;
5468
5469   if (priv->position == position)
5470     return;
5471
5472   len = clutter_text_buffer_get_length (get_buffer (self));
5473
5474   if (position < 0 || position >= len)
5475     priv->position = -1;
5476   else
5477     priv->position = position;
5478
5479   /* Forget the target x position so that it will be recalculated next
5480      time the cursor is moved up or down */
5481   priv->x_pos = -1;
5482
5483   clutter_text_queue_redraw (CLUTTER_ACTOR (self));
5484
5485   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]);
5486 }
5487
5488 /**
5489  * clutter_text_set_cursor_size:
5490  * @self: a #ClutterText
5491  * @size: the size of the cursor, in pixels, or -1 to use the
5492  *   default value
5493  *
5494  * Sets the size of the cursor of a #ClutterText. The cursor
5495  * will only be visible if the #ClutterText:cursor-visible property
5496  * is set to %TRUE.
5497  *
5498  * Since: 1.0
5499  */
5500 void
5501 clutter_text_set_cursor_size (ClutterText *self,
5502                               gint         size)
5503 {
5504   ClutterTextPrivate *priv;
5505
5506   g_return_if_fail (CLUTTER_IS_TEXT (self));
5507
5508   priv = self->priv;
5509
5510   if (priv->cursor_size != size)
5511     {
5512       if (size < 0)
5513         size = DEFAULT_CURSOR_SIZE;
5514
5515       priv->cursor_size = size;
5516
5517       clutter_text_queue_redraw (CLUTTER_ACTOR (self));
5518
5519       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_SIZE]);
5520     }
5521 }
5522
5523 /**
5524  * clutter_text_get_cursor_size:
5525  * @self: a #ClutterText
5526  *
5527  * Retrieves the size of the cursor of a #ClutterText actor.
5528  *
5529  * Return value: the size of the cursor, in pixels
5530  *
5531  * Since: 1.0
5532  */
5533 guint
5534 clutter_text_get_cursor_size (ClutterText *self)
5535 {
5536   g_return_val_if_fail (CLUTTER_IS_TEXT (self), DEFAULT_CURSOR_SIZE);
5537
5538   return self->priv->cursor_size;
5539 }
5540
5541 /**
5542  * clutter_text_set_password_char:
5543  * @self: a #ClutterText
5544  * @wc: a Unicode character, or 0 to unset the password character
5545  *
5546  * Sets the character to use in place of the actual text in a
5547  * password text actor.
5548  *
5549  * If @wc is 0 the text will be displayed as it is entered in the
5550  * #ClutterText actor.
5551  *
5552  * Since: 1.0
5553  */
5554 void
5555 clutter_text_set_password_char (ClutterText *self,
5556                                 gunichar     wc)
5557 {
5558   ClutterTextPrivate *priv;
5559
5560   g_return_if_fail (CLUTTER_IS_TEXT (self));
5561
5562   priv = self->priv;
5563
5564   if (priv->password_char != wc)
5565     {
5566       priv->password_char = wc;
5567
5568       clutter_text_dirty_cache (self);
5569       clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5570
5571       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_PASSWORD_CHAR]);
5572     }
5573 }
5574
5575 /**
5576  * clutter_text_get_password_char:
5577  * @self: a #ClutterText
5578  *
5579  * Retrieves the character to use in place of the actual text
5580  * as set by clutter_text_set_password_char().
5581  *
5582  * Return value: a Unicode character or 0 if the password
5583  *   character is not set
5584  *
5585  * Since: 1.0
5586  */
5587 gunichar
5588 clutter_text_get_password_char (ClutterText *self)
5589 {
5590   g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0);
5591
5592   return self->priv->password_char;
5593 }
5594
5595 /**
5596  * clutter_text_set_max_length:
5597  * @self: a #ClutterText
5598  * @max: the maximum number of characters allowed in the text actor; 0
5599  *   to disable or -1 to set the length of the current string
5600  *
5601  * Sets the maximum allowed length of the contents of the actor. If the
5602  * current contents are longer than the given length, then they will be
5603  * truncated to fit.
5604  *
5605  * Since: 1.0
5606  */
5607 void
5608 clutter_text_set_max_length (ClutterText *self,
5609                              gint         max)
5610 {
5611   g_return_if_fail (CLUTTER_IS_TEXT (self));
5612   clutter_text_buffer_set_max_length (get_buffer (self), max);
5613 }
5614
5615 /**
5616  * clutter_text_get_max_length:
5617  * @self: a #ClutterText
5618  *
5619  * Gets the maximum length of text that can be set into a text actor.
5620  *
5621  * See clutter_text_set_max_length().
5622  *
5623  * Return value: the maximum number of characters.
5624  *
5625  * Since: 1.0
5626  */
5627 gint
5628 clutter_text_get_max_length (ClutterText *self)
5629 {
5630   g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0);
5631
5632   return clutter_text_buffer_get_max_length (get_buffer (self));
5633 }
5634
5635 /**
5636  * clutter_text_insert_unichar:
5637  * @self: a #ClutterText
5638  * @wc: a Unicode character
5639  *
5640  * Inserts @wc at the current cursor position of a
5641  * #ClutterText actor.
5642  *
5643  * Since: 1.0
5644  */
5645 void
5646 clutter_text_insert_unichar (ClutterText *self,
5647                              gunichar     wc)
5648 {
5649   ClutterTextPrivate *priv;
5650   GString *new;
5651
5652   priv = self->priv;
5653
5654   new = g_string_new ("");
5655   g_string_append_unichar (new, wc);
5656
5657   clutter_text_buffer_insert_text (get_buffer (self), priv->position, new->str, 1);
5658
5659   g_string_free (new, TRUE);
5660 }
5661
5662 /**
5663  * clutter_text_insert_text:
5664  * @self: a #ClutterText
5665  * @text: the text to be inserted
5666  * @position: the position of the insertion, or -1
5667  *
5668  * Inserts @text into a #ClutterActor at the given position.
5669  *
5670  * If @position is a negative number, the text will be appended
5671  * at the end of the current contents of the #ClutterText.
5672  *
5673  * The position is expressed in characters, not in bytes.
5674  *
5675  * Since: 1.0
5676  */
5677 void
5678 clutter_text_insert_text (ClutterText *self,
5679                           const gchar *text,
5680                           gssize       position)
5681 {
5682   g_return_if_fail (CLUTTER_IS_TEXT (self));
5683   g_return_if_fail (text != NULL);
5684
5685   clutter_text_buffer_insert_text (get_buffer (self), position, text,
5686                                    g_utf8_strlen (text, -1));
5687 }
5688
5689 /**
5690  * clutter_text_delete_text:
5691  * @self: a #ClutterText
5692  * @start_pos: starting position
5693  * @end_pos: ending position
5694  *
5695  * Deletes the text inside a #ClutterText actor between @start_pos
5696  * and @end_pos.
5697  *
5698  * The starting and ending positions are expressed in characters,
5699  * not in bytes.
5700  *
5701  * Since: 1.0
5702  */
5703 void
5704 clutter_text_delete_text (ClutterText *self,
5705                           gssize       start_pos,
5706                           gssize       end_pos)
5707 {
5708   g_return_if_fail (CLUTTER_IS_TEXT (self));
5709
5710   clutter_text_buffer_delete_text (get_buffer (self), start_pos, end_pos - start_pos);
5711 }
5712
5713 /**
5714  * clutter_text_delete_chars:
5715  * @self: a #ClutterText
5716  * @n_chars: the number of characters to delete
5717  *
5718  * Deletes @n_chars inside a #ClutterText actor, starting from the
5719  * current cursor position.
5720  *
5721  * Somewhat awkwardly, the cursor position is decremented by the same
5722  * number of characters you've deleted.
5723  *
5724  * Since: 1.0
5725  */
5726 void
5727 clutter_text_delete_chars (ClutterText *self,
5728                            guint        n_chars)
5729 {
5730   ClutterTextPrivate *priv;
5731
5732   g_return_if_fail (CLUTTER_IS_TEXT (self));
5733
5734   priv = self->priv;
5735
5736   clutter_text_buffer_delete_text (get_buffer (self), priv->position, n_chars);
5737
5738   if (priv->position > 0)
5739     clutter_text_set_cursor_position (self, priv->position - n_chars);
5740 }
5741
5742 /**
5743  * clutter_text_get_chars:
5744  * @self: a #ClutterText
5745  * @start_pos: start of text, in characters
5746  * @end_pos: end of text, in characters
5747  *
5748  * Retrieves the contents of the #ClutterText actor between
5749  * @start_pos and @end_pos, but not including @end_pos.
5750  *
5751  * The positions are specified in characters, not in bytes.
5752  *
5753  * Return value: a newly allocated string with the contents of
5754  *   the text actor between the specified positions. Use g_free()
5755  *   to free the resources when done
5756  *
5757  * Since: 1.0
5758  */
5759 gchar *
5760 clutter_text_get_chars (ClutterText *self,
5761                         gssize       start_pos,
5762                         gssize       end_pos)
5763 {
5764   gint start_index, end_index;
5765   guint n_chars;
5766   const gchar *text;
5767
5768   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
5769
5770   n_chars = clutter_text_buffer_get_length (get_buffer (self));
5771   text = clutter_text_buffer_get_text (get_buffer (self));
5772
5773   if (end_pos < 0)
5774     end_pos = n_chars;
5775
5776   start_pos = MIN (n_chars, start_pos);
5777   end_pos = MIN (n_chars, end_pos);
5778
5779   start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
5780   end_index   = g_utf8_offset_to_pointer (text, end_pos) - text;
5781
5782   return g_strndup (text + start_index, end_index - start_index);
5783 }
5784
5785 /**
5786  * clutter_text_set_single_line_mode:
5787  * @self: a #ClutterText
5788  * @single_line: whether to enable single line mode
5789  *
5790  * Sets whether a #ClutterText actor should be in single line mode
5791  * or not. Only editable #ClutterText<!-- -->s can be in single line
5792  * mode.
5793  *
5794  * A text actor in single line mode will not wrap text and will clip
5795  * the visible area to the predefined size. The contents of the
5796  * text actor will scroll to display the end of the text if its length
5797  * is bigger than the allocated width.
5798  *
5799  * When setting the single line mode the #ClutterText:activatable
5800  * property is also set as a side effect. Instead of entering a new
5801  * line character, the text actor will emit the #ClutterText::activate
5802  * signal.
5803  *
5804  * Since: 1.0
5805  */
5806 void
5807 clutter_text_set_single_line_mode (ClutterText *self,
5808                                    gboolean     single_line)
5809 {
5810   ClutterTextPrivate *priv;
5811
5812   g_return_if_fail (CLUTTER_IS_TEXT (self));
5813
5814   priv = self->priv;
5815
5816   if (priv->single_line_mode != single_line)
5817     {
5818       g_object_freeze_notify (G_OBJECT (self));
5819
5820       priv->single_line_mode = single_line;
5821
5822       if (priv->single_line_mode)
5823         {
5824           priv->activatable = TRUE;
5825
5826           g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACTIVATABLE]);
5827         }
5828
5829       clutter_text_dirty_cache (self);
5830       clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5831
5832       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SINGLE_LINE_MODE]);
5833
5834       g_object_thaw_notify (G_OBJECT (self));
5835     }
5836 }
5837
5838 /**
5839  * clutter_text_get_single_line_mode:
5840  * @self: a #ClutterText
5841  *
5842  * Retrieves whether the #ClutterText actor is in single line mode.
5843  *
5844  * Return value: %TRUE if the #ClutterText actor is in single line mode
5845  *
5846  * Since: 1.0
5847  */
5848 gboolean
5849 clutter_text_get_single_line_mode (ClutterText *self)
5850 {
5851   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
5852
5853   return self->priv->single_line_mode;
5854 }
5855
5856 /**
5857  * clutter_text_set_preedit_string:
5858  * @self: a #ClutterText
5859  * @preedit_str: (allow-none): the pre-edit string, or %NULL to unset it
5860  * @preedit_attrs: (allow-none): the pre-edit string attributes
5861  * @cursor_pos: the cursor position for the pre-edit string
5862  *
5863  * Sets, or unsets, the pre-edit string. This function is useful
5864  * for input methods to display a string (with eventual specific
5865  * Pango attributes) before it is entered inside the #ClutterText
5866  * buffer.
5867  *
5868  * The preedit string and attributes are ignored if the #ClutterText
5869  * actor is not editable.
5870  *
5871  * This function should not be used by applications
5872  *
5873  * Since: 1.2
5874  */
5875 void
5876 clutter_text_set_preedit_string (ClutterText   *self,
5877                                  const gchar   *preedit_str,
5878                                  PangoAttrList *preedit_attrs,
5879                                  guint          cursor_pos)
5880 {
5881   ClutterTextPrivate *priv;
5882
5883   g_return_if_fail (CLUTTER_IS_TEXT (self));
5884
5885   priv = self->priv;
5886
5887   g_free (priv->preedit_str);
5888   priv->preedit_str = NULL;
5889
5890   if (priv->preedit_attrs != NULL)
5891     {
5892       pango_attr_list_unref (priv->preedit_attrs);
5893       priv->preedit_attrs = NULL;
5894     }
5895
5896   priv->preedit_n_chars = 0;
5897   priv->preedit_cursor_pos = 0;
5898
5899   if (preedit_str == NULL || *preedit_str == '\0')
5900     priv->preedit_set = FALSE;
5901   else
5902     {
5903       priv->preedit_str = g_strdup (preedit_str);
5904
5905       if (priv->preedit_str != NULL)
5906         priv->preedit_n_chars = g_utf8_strlen (priv->preedit_str, -1);
5907       else
5908         priv->preedit_n_chars = 0;
5909
5910       if (preedit_attrs != NULL)
5911         priv->preedit_attrs = pango_attr_list_ref (preedit_attrs);
5912
5913       priv->preedit_cursor_pos =
5914         CLAMP (cursor_pos, 0, priv->preedit_n_chars);
5915
5916       priv->preedit_set = TRUE;
5917     }
5918
5919   clutter_text_dirty_cache (self);
5920   clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
5921 }
5922
5923
5924 /**
5925  * clutter_text_get_layout_offsets:
5926  * @self: a #ClutterText
5927  * @x: (out): location to store X offset of layout, or %NULL
5928  * @y: (out): location to store Y offset of layout, or %NULL
5929  *
5930  * Obtains the coordinates where the #ClutterText will draw the #PangoLayout
5931  * representing the text.
5932  *
5933  * Since: 1.8
5934  */
5935 void
5936 clutter_text_get_layout_offsets (ClutterText *self,
5937                                  gint        *x,
5938                                  gint        *y)
5939 {
5940   ClutterTextPrivate *priv;
5941
5942   g_return_if_fail (CLUTTER_IS_TEXT (self));
5943
5944   priv = self->priv;
5945
5946   if (x != NULL)
5947     *x = priv->text_x;
5948
5949   if (y != NULL)
5950     *y = priv->text_y;
5951 }