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