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 * /* the order is important */
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 < 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