clutter-text.c 187 KB
Newer Older
1 2 3 4 5
/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
6
 * Copyright (C) 2008  Intel Corporation.
7
 *
8 9
 * Authored By: Øyvind Kolås <pippin@o-hand.com>
 *              Emmanuele Bassi <ebassi@linux.intel.com>
10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40
/**
 * SECTION:clutter-text
 * @short_description: An actor for displaying and editing text
 *
 * #ClutterText is an actor that displays custom text using Pango
 * as the text rendering engine.
 *
 * #ClutterText also allows inline editing of the text if the
 * actor is set editable using clutter_text_set_editable().
 *
 * Selection using keyboard or pointers can be enabled using
 * clutter_text_set_selectable().
 *
 * #ClutterText is available since Clutter 1.0
 */

41
#ifdef HAVE_CONFIG_H
42
#include "clutter-build-config.h"
43 44 45
#endif

#include <string.h>
46
#include <math.h>
47 48

#include "clutter-text.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
49

50
#include "clutter-actor-private.h"
51
#include "clutter-animatable.h"
52
#include "clutter-backend-private.h"
53
#include "clutter-binding-pool.h"
54
#include "clutter-color.h"
55 56
#include "clutter-debug.h"
#include "clutter-enum-types.h"
57
#include "clutter-keysyms.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
58
#include "clutter-main.h"
59
#include "clutter-marshal.h"
60
#include "clutter-private.h"    /* includes <cogl-pango/cogl-pango.h> */
61
#include "clutter-property-transition.h"
62
#include "clutter-text-buffer.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
63
#include "clutter-units.h"
64
#include "clutter-paint-volume-private.h"
65
#include "clutter-scriptable.h"
66
#include "clutter-input-focus.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
67

68 69 70
/* cursor width in pixels */
#define DEFAULT_CURSOR_SIZE     2

71 72 73
/* vertical padding for the cursor */
#define CURSOR_Y_PADDING        2

74
/* We need at least three cached layouts to run the allocation without
75 76 77 78 79
 * regenerating a new layout. First the layout will be generated at
 * full width to get the preferred width, then it will be generated at
 * the preferred width to get the preferred height and then it might
 * be regenerated at a different width to get the height for the
 * actual allocated width
80 81 82
 *
 * since we might get multiple queries from layout managers doing a
 * double-pass allocations, like tabular ones, we should use 6 slots
83
 */
84
#define N_CACHED_LAYOUTS        6
85 86 87 88 89

typedef struct _LayoutCache     LayoutCache;

struct _LayoutCache
{
90 91 92 93
  /* Cached layout. Pango internally caches the computed extents
   * when they are requested so there is no need to cache that as
   * well
   */
94 95
  PangoLayout *layout;

96 97 98
  /* A number representing the age of this cache (so that when a
   * new layout is needed the last used cache is replaced)
   */
99
  guint age;
100
};
101

102 103 104 105 106 107
struct _ClutterTextInputFocus
{
  ClutterInputFocus parent_instance;
  ClutterText *text;
};

108 109
struct _ClutterTextPrivate
{
110 111
  PangoFontDescription *font_desc;

112
  /* the displayed text */
113
  ClutterTextBuffer *buffer;
114

115
  gchar *font_name;
116

117
  gchar *preedit_str;
118 119 120 121 122 123

  ClutterColor text_color;

  LayoutCache cached_layouts[N_CACHED_LAYOUTS];
  guint cache_age;

124
  /* These are the attributes set by the attributes property */
125
  PangoAttrList *attrs;
126 127 128 129 130 131
  /* These are the attributes derived from the text when the
     use-markup property is set */
  PangoAttrList *markup_attrs;
  /* This is the combination of the above two lists. It is set to NULL
     whenever either of them changes and then regenerated by merging
     the two lists whenever a layout is needed */
132
  PangoAttrList *effective_attrs;
133 134 135
  /* These are the attributes for the preedit string. These are merged
     with the effective attributes into a temporary list before
     creating a layout */
136
  PangoAttrList *preedit_attrs;
137

138 139 140 141 142 143
  /* current cursor position */
  gint position;

  /* current 'other end of selection' position */
  gint selection_bound;

144
  /* the x position in the PangoLayout, used to
145 146
   * avoid drifting when repeatedly moving up|down
   */
147
  gint x_pos;
148

149 150 151 152
  /* the x position of the PangoLayout when in
   * single line mode, to scroll the contents of the
   * text actor
   */
153 154
  gint text_x;

155 156 157 158
  /* the y position of the PangoLayout, fixed to 0 by
   * default for now */
  gint text_y;

159
  /* Where to draw the cursor */
160
  ClutterRect cursor_rect;
161
  ClutterColor cursor_color;
162
  guint cursor_size;
163

164 165 166 167
  /* Box representing the paint volume. The box is lazily calculated
     and cached */
  ClutterPaintVolume paint_volume;

168 169 170
  guint preedit_cursor_pos;
  gint preedit_n_chars;

171 172
  ClutterColor selection_color;

173 174
  ClutterColor selected_text_color;

175
  gunichar password_char;
176

177 178 179
  guint password_hint_id;
  guint password_hint_timeout;

180
  /* Signal handler for when the backend changes its font settings */
181
  guint settings_changed_id;
182 183 184

  /* Signal handler for when the :text-direction changes */
  guint direction_changed_id;
185

186
  ClutterInputFocus *input_focus;
187 188
  ClutterInputContentHintFlags input_hints;
  ClutterInputContentPurpose input_purpose;
189

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
  /* bitfields */
  guint alignment               : 2;
  guint wrap                    : 1;
  guint use_underline           : 1;
  guint use_markup              : 1;
  guint ellipsize               : 3;
  guint single_line_mode        : 1;
  guint wrap_mode               : 3;
  guint justify                 : 1;
  guint editable                : 1;
  guint cursor_visible          : 1;
  guint activatable             : 1;
  guint selectable              : 1;
  guint selection_color_set     : 1;
  guint in_select_drag          : 1;
205
  guint in_select_touch         : 1;
206 207 208 209 210 211
  guint cursor_color_set        : 1;
  guint preedit_set             : 1;
  guint is_default_font         : 1;
  guint has_focus               : 1;
  guint selected_text_color_set : 1;
  guint paint_volume_valid      : 1;
212 213
  guint show_password_hint      : 1;
  guint password_hint_visible   : 1;
214
  guint resolved_direction      : 4;
215 216 217 218 219
};

enum
{
  PROP_0,
220

221
  PROP_BUFFER,
222
  PROP_FONT_NAME,
223
  PROP_FONT_DESCRIPTION,
224 225 226
  PROP_TEXT,
  PROP_COLOR,
  PROP_USE_MARKUP,
227
  PROP_ATTRIBUTES,
228
  PROP_LINE_ALIGNMENT,
229 230
  PROP_LINE_WRAP,
  PROP_LINE_WRAP_MODE,
231 232
  PROP_JUSTIFY,
  PROP_ELLIPSIZE,
233
  PROP_POSITION, /* XXX:2.0 - remove */
234
  PROP_SELECTION_BOUND,
235 236
  PROP_SELECTION_COLOR,
  PROP_SELECTION_COLOR_SET,
237 238 239
  PROP_CURSOR_VISIBLE,
  PROP_CURSOR_COLOR,
  PROP_CURSOR_COLOR_SET,
240
  PROP_CURSOR_SIZE,
241
  PROP_CURSOR_POSITION,
242 243
  PROP_EDITABLE,
  PROP_SELECTABLE,
244
  PROP_ACTIVATABLE,
245
  PROP_PASSWORD_CHAR,
246
  PROP_MAX_LENGTH,
247
  PROP_SINGLE_LINE_MODE,
248 249
  PROP_SELECTED_TEXT_COLOR,
  PROP_SELECTED_TEXT_COLOR_SET,
250 251
  PROP_INPUT_HINTS,
  PROP_INPUT_PURPOSE,
252 253

  PROP_LAST
254 255
};

256 257
static GParamSpec *obj_props[PROP_LAST];

258 259 260
enum
{
  TEXT_CHANGED,
261
  CURSOR_EVENT, /* XXX:2.0 - remove */
262
  ACTIVATE,
263 264
  INSERT_TEXT,
  DELETE_TEXT,
265
  CURSOR_CHANGED,
266

267 268 269
  LAST_SIGNAL
};

270
static guint text_signals[LAST_SIGNAL] = { 0, };
271

272
static void clutter_text_settings_changed_cb (ClutterText *text);
273 274 275
static void buffer_connect_signals (ClutterText *self);
static void buffer_disconnect_signals (ClutterText *self);
static ClutterTextBuffer *get_buffer (ClutterText *self);
276

277 278 279 280 281 282 283 284
static const ClutterColor default_cursor_color    = {   0,   0,   0, 255 };
static const ClutterColor default_selection_color = {   0,   0,   0, 255 };
static const ClutterColor default_text_color      = {   0,   0,   0, 255 };
static const ClutterColor default_selected_text_color = {   0,   0,   0, 255 };

static ClutterAnimatableIface *parent_animatable_iface = NULL;
static ClutterScriptableIface *parent_scriptable_iface = NULL;

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
/* ClutterTextInputFocus */
#define CLUTTER_TYPE_TEXT_INPUT_FOCUS (clutter_text_input_focus_get_type ())

G_DECLARE_FINAL_TYPE (ClutterTextInputFocus, clutter_text_input_focus,
                      CLUTTER, TEXT_INPUT_FOCUS, ClutterInputFocus)
G_DEFINE_TYPE (ClutterTextInputFocus, clutter_text_input_focus,
               CLUTTER_TYPE_INPUT_FOCUS)

static void
clutter_text_input_focus_request_surrounding (ClutterInputFocus *focus)
{
  ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;
  ClutterTextBuffer *buffer;
  const gchar *text;
  gint anchor_pos, cursor_pos;

  buffer = clutter_text_get_buffer (clutter_text);
  text = clutter_text_buffer_get_text (buffer);

  cursor_pos = clutter_text_get_cursor_position (clutter_text);
  if (cursor_pos < 0)
    cursor_pos = clutter_text_buffer_get_length (buffer);

  anchor_pos = clutter_text_get_selection_bound (clutter_text);
  if (anchor_pos < 0)
    anchor_pos = cursor_pos;

  clutter_input_focus_set_surrounding (focus, text,
                                       g_utf8_offset_to_pointer (text, cursor_pos) - text,
                                       g_utf8_offset_to_pointer (text, anchor_pos) - text);
}

static void
clutter_text_input_focus_delete_surrounding (ClutterInputFocus *focus,
                                             guint              offset,
                                             guint              len)
{
  ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;

  if (clutter_text_get_editable (clutter_text))
    clutter_text_delete_text (clutter_text, offset, len);
}

static void
clutter_text_input_focus_commit_text (ClutterInputFocus *focus,
                                      const gchar       *text)
{
  ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;

  if (clutter_text_get_editable (clutter_text))
    {
      clutter_text_delete_selection (clutter_text);
      clutter_text_insert_text (clutter_text, text,
                                clutter_text_get_cursor_position (clutter_text));
339
      clutter_text_set_preedit_string (clutter_text, NULL, NULL, 0);
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
    }
}

static void
clutter_text_input_focus_set_preedit_text (ClutterInputFocus *focus,
                                           const gchar       *preedit_text,
                                           guint              cursor_pos)
{
  ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;

  if (clutter_text_get_editable (clutter_text))
    {
      PangoAttrList *list;

      list = pango_attr_list_new ();
      pango_attr_list_insert (list, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE));
      clutter_text_set_preedit_string (clutter_text,
                                       preedit_text, list,
                                       cursor_pos);
      pango_attr_list_unref (list);
    }
}

static void
clutter_text_input_focus_class_init (ClutterTextInputFocusClass *klass)
{
  ClutterInputFocusClass *focus_class = CLUTTER_INPUT_FOCUS_CLASS (klass);

  focus_class->request_surrounding = clutter_text_input_focus_request_surrounding;
  focus_class->delete_surrounding = clutter_text_input_focus_delete_surrounding;
  focus_class->commit_text = clutter_text_input_focus_commit_text;
  focus_class->set_preedit_text = clutter_text_input_focus_set_preedit_text;
}

static void
clutter_text_input_focus_init (ClutterTextInputFocus *focus)
{
}

static ClutterInputFocus *
clutter_text_input_focus_new (ClutterText *text)
{
  ClutterTextInputFocus *focus;

  focus = g_object_new (CLUTTER_TYPE_TEXT_INPUT_FOCUS, NULL);
  focus->text = text;

  return CLUTTER_INPUT_FOCUS (focus);
}

/* ClutterText */
391 392 393 394 395 396 397 398 399 400 401 402
static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
static void clutter_animatable_iface_init (ClutterAnimatableIface *iface);

G_DEFINE_TYPE_WITH_CODE (ClutterText,
                         clutter_text,
                         CLUTTER_TYPE_ACTOR,
                         G_ADD_PRIVATE (ClutterText)
                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
                                                clutter_scriptable_iface_init)
                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_ANIMATABLE,
                                                clutter_animatable_iface_init));

Emmanuele Bassi's avatar
Emmanuele Bassi committed
403
static inline void
404 405 406 407 408 409 410 411 412 413 414
clutter_text_dirty_paint_volume (ClutterText *text)
{
  ClutterTextPrivate *priv = text->priv;

  if (priv->paint_volume_valid)
    {
      clutter_paint_volume_free (&priv->paint_volume);
      priv->paint_volume_valid = FALSE;
    }
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
415
static inline void
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
clutter_text_queue_redraw (ClutterActor *self)
{
  /* This is a wrapper for clutter_actor_queue_redraw that also
     dirties the cached paint volume. It would be nice if we could
     just override the default implementation of the queue redraw
     signal to do this instead but that doesn't work because the
     signal isn't immediately emitted when queue_redraw is called.
     Clutter will however immediately call get_paint_volume when
     queue_redraw is called so we do need to dirty it immediately. */

  clutter_text_dirty_paint_volume (CLUTTER_TEXT (self));

  clutter_actor_queue_redraw (self);
}

431 432 433 434 435
static gboolean
clutter_text_should_draw_cursor (ClutterText *self)
{
  ClutterTextPrivate *priv = self->priv;

436 437 438
  return (priv->editable || priv->selectable) &&
    priv->cursor_visible &&
    priv->has_focus;
439 440
}

441 442 443
#define clutter_actor_queue_redraw \
  Please_use_clutter_text_queue_redraw_instead

444
#define offset_real(t,p)        ((p) == -1 ? g_utf8_strlen ((t), -1) : (p))
445

446 447 448 449
static gint
offset_to_bytes (const gchar *text,
                 gint         pos)
{
450
  const gchar *ptr;
451 452 453 454

  if (pos < 0)
    return strlen (text);

455 456 457 458 459
  /* Loop over each character in the string until we either reach the
     end or the requested position */
  for (ptr = text; *ptr && pos-- > 0; ptr = g_utf8_next_char (ptr));

  return ptr - text;
460
}
461

462
#define bytes_to_offset(t,p)    (g_utf8_pointer_to_offset ((t), (t) + (p)))
463

464 465
static inline void
clutter_text_clear_selection (ClutterText *self)
466
{
467 468
  ClutterTextPrivate *priv = self->priv;

469 470 471
  if (priv->selection_bound != priv->position)
    {
      priv->selection_bound = priv->position;
472
      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
473
      clutter_text_queue_redraw (CLUTTER_ACTOR (self));
474
    }
475 476
}

477 478 479 480 481 482 483 484 485 486 487 488
static gboolean
clutter_text_is_empty (ClutterText *self)
{
  if (self->priv->buffer == NULL)
    return TRUE;

  if (clutter_text_buffer_get_length (self->priv->buffer) == 0)
    return TRUE;

  return FALSE;
}

489 490 491 492
static gchar *
clutter_text_get_display_text (ClutterText *self)
{
  ClutterTextPrivate *priv = self->priv;
493 494 495
  ClutterTextBuffer *buffer;
  const gchar *text;

496 497 498 499 500 501 502
  /* short-circuit the case where the buffer is unset or it's empty,
   * to avoid creating a pointless TextBuffer and emitting
   * notifications with it
   */
  if (clutter_text_is_empty (self))
    return g_strdup ("");

503 504
  buffer = get_buffer (self);
  text = clutter_text_buffer_get_text (buffer);
505

506 507 508
  /* simple short-circuit to avoid going through GString
   * with an empty text and a password char set
   */
509
  if (text[0] == '\0')
510 511
    return g_strdup ("");

512 513
  if (G_LIKELY (priv->password_char == 0))
    return g_strdup (text);
514 515
  else
    {
516
      GString *str;
517 518 519
      gunichar invisible_char;
      gchar buf[7];
      gint char_len, i;
520
      guint n_chars;
521

522 523
      n_chars = clutter_text_buffer_get_length (buffer);
      str = g_string_sized_new (clutter_text_buffer_get_bytes (buffer));
524 525 526 527 528 529 530 531 532
      invisible_char = priv->password_char;

      /* we need to convert the string built of invisible
       * characters into UTF-8 for it to be fed to the Pango
       * layout
       */
      memset (buf, 0, sizeof (buf));
      char_len = g_unichar_to_utf8 (invisible_char, buf);

533 534 535 536
      if (priv->show_password_hint && priv->password_hint_visible)
        {
          char *last_char;

537
          for (i = 0; i < n_chars - 1; i++)
538 539
            g_string_append_len (str, buf, char_len);

540
          last_char = g_utf8_offset_to_pointer (text, n_chars - 1);
541 542 543 544
          g_string_append (str, last_char);
        }
      else
        {
545
          for (i = 0; i < n_chars; i++)
546 547
            g_string_append_len (str, buf, char_len);
        }
548 549 550 551 552

      return g_string_free (str, FALSE);
    }
}

Emmanuele Bassi's avatar
Emmanuele Bassi committed
553
static inline void
554 555 556 557 558 559
clutter_text_ensure_effective_attributes (ClutterText *self)
{
  ClutterTextPrivate *priv = self->priv;

  /* If we already have the effective attributes then we don't need to
     do anything */
Emmanuele Bassi's avatar
Emmanuele Bassi committed
560 561 562
  if (priv->effective_attrs != NULL)
    return;

563 564 565
  /* Same as if we don't have any attribute at all.
   * We also ignore markup attributes for editable. */
  if (priv->attrs == NULL && (priv->editable || priv->markup_attrs == NULL))
Emmanuele Bassi's avatar
Emmanuele Bassi committed
566 567 568
    return;

  if (priv->attrs != NULL)
569
    {
570 571 572
      /* If there are no markup attributes, or if this is editable (in which
       * case we ignore markup), then we can just use these attrs directly */
      if (priv->editable || priv->markup_attrs == NULL)
Emmanuele Bassi's avatar
Emmanuele Bassi committed
573 574
        priv->effective_attrs = pango_attr_list_ref (priv->attrs);
      else
575
        {
Emmanuele Bassi's avatar
Emmanuele Bassi committed
576 577 578
          /* Otherwise we need to merge the two lists */
          PangoAttrIterator *iter;
          GSList *attributes, *l;
579

Emmanuele Bassi's avatar
Emmanuele Bassi committed
580
          priv->effective_attrs = pango_attr_list_copy (priv->markup_attrs);
581

Emmanuele Bassi's avatar
Emmanuele Bassi committed
582 583 584 585
          iter = pango_attr_list_get_iterator (priv->attrs);
          do
            {
              attributes = pango_attr_iterator_get_attrs (iter);
586

Emmanuele Bassi's avatar
Emmanuele Bassi committed
587 588 589
              for (l = attributes; l != NULL; l = l->next)
                {
                  PangoAttribute *attr = l->data;
590

Emmanuele Bassi's avatar
Emmanuele Bassi committed
591
                  pango_attr_list_insert (priv->effective_attrs, attr);
592
                }
593

Emmanuele Bassi's avatar
Emmanuele Bassi committed
594
              g_slist_free (attributes);
595
            }
Emmanuele Bassi's avatar
Emmanuele Bassi committed
596 597 598
          while (pango_attr_iterator_next (iter));

          pango_attr_iterator_destroy (iter);
599
        }
Emmanuele Bassi's avatar
Emmanuele Bassi committed
600 601 602 603 604
    }
  else if (priv->markup_attrs != NULL)
    {
      /* We can just use the markup attributes directly */
      priv->effective_attrs = pango_attr_list_ref (priv->markup_attrs);
605 606 607
    }
}

608
static PangoLayout *
609 610 611 612
clutter_text_create_layout_no_cache (ClutterText       *text,
				     gint               width,
				     gint               height,
				     PangoEllipsizeMode ellipsize)
613 614 615
{
  ClutterTextPrivate *priv = text->priv;
  PangoLayout *layout;
616 617
  gchar *contents;
  gsize contents_len;
618

619
  layout = clutter_actor_create_pango_layout (CLUTTER_ACTOR (text), NULL);
620 621
  pango_layout_set_font_description (layout, priv->font_desc);

622 623 624 625
  contents = clutter_text_get_display_text (text);
  contents_len = strlen (contents);

  if (priv->editable && priv->preedit_set)
626
    {
627 628 629 630 631 632
      GString *tmp = g_string_new (contents);
      PangoAttrList *tmp_attrs = pango_attr_list_new ();
      gint cursor_index;

      if (priv->position == 0)
        cursor_index = 0;
633
      else
634
        cursor_index = offset_to_bytes (contents, priv->position);
635

636
      g_string_insert (tmp, cursor_index, priv->preedit_str);
637

638
      pango_layout_set_text (layout, tmp->str, tmp->len);
639

640 641 642 643 644 645 646 647
      if (priv->preedit_attrs != NULL)
        {
          pango_attr_list_splice (tmp_attrs, priv->preedit_attrs,
                                  cursor_index,
                                  strlen (priv->preedit_str));

          pango_layout_set_attributes (layout, tmp_attrs);
        }
648

649 650
      g_string_free (tmp, TRUE);
      pango_attr_list_unref (tmp_attrs);
651
    }
652
  else
653 654 655 656 657 658 659 660 661 662
    {
      PangoDirection pango_dir;

      if (priv->password_char != 0)
        pango_dir = PANGO_DIRECTION_NEUTRAL;
      else
        pango_dir = pango_find_base_dir (contents, contents_len);

      if (pango_dir == PANGO_DIRECTION_NEUTRAL)
        {
663
          ClutterBackend *backend = clutter_get_default_backend ();
664 665
          ClutterTextDirection text_dir;

666 667
          if (clutter_actor_has_key_focus (CLUTTER_ACTOR (text)))
            pango_dir = _clutter_backend_get_keymap_direction (backend);
668
          else
669 670 671 672 673 674 675 676
            {
              text_dir = clutter_actor_get_text_direction (CLUTTER_ACTOR (text));

              if (text_dir == CLUTTER_TEXT_DIRECTION_RTL)
                pango_dir = PANGO_DIRECTION_RTL;
              else
                pango_dir = PANGO_DIRECTION_LTR;
           }
677 678 679 680
        }

      pango_context_set_base_dir (clutter_actor_get_pango_context (CLUTTER_ACTOR (text)), pango_dir);

681 682
      priv->resolved_direction = pango_dir;

683 684
      pango_layout_set_text (layout, contents, contents_len);
    }
685

686 687 688
  /* This will merge the markup attributes and the attributes
   * property if needed */
  clutter_text_ensure_effective_attributes (text);
689

690 691
  if (priv->effective_attrs != NULL)
    pango_layout_set_attributes (layout, priv->effective_attrs);
692 693 694 695

  pango_layout_set_alignment (layout, priv->alignment);
  pango_layout_set_single_paragraph_mode (layout, priv->single_line_mode);
  pango_layout_set_justify (layout, priv->justify);
696
  pango_layout_set_wrap (layout, priv->wrap_mode);
697

698 699 700
  pango_layout_set_ellipsize (layout, ellipsize);
  pango_layout_set_width (layout, width);
  pango_layout_set_height (layout, height);
701

702 703
  g_free (contents);

704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
  return layout;
}

static void
clutter_text_dirty_cache (ClutterText *text)
{
  ClutterTextPrivate *priv = text->priv;
  int i;

  /* Delete the cached layouts so they will be recreated the next time
     they are needed */
  for (i = 0; i < N_CACHED_LAYOUTS; i++)
    if (priv->cached_layouts[i].layout)
      {
	g_object_unref (priv->cached_layouts[i].layout);
	priv->cached_layouts[i].layout = NULL;
      }
721 722

  clutter_text_dirty_paint_volume (text);
723 724
}

725 726 727 728 729 730
/*
 * clutter_text_set_font_description_internal:
 * @self: a #ClutterText
 * @desc: a #PangoFontDescription
 *
 * Sets @desc as the font description to be used by the #ClutterText
731
 * actor. The #PangoFontDescription is copied.
732 733 734 735 736 737 738 739
 *
 * This function will also set the :font-name field as a side-effect
 *
 * This function will evict the layout cache, and queue a relayout if
 * the #ClutterText actor has contents.
 */
static inline void
clutter_text_set_font_description_internal (ClutterText          *self,
740 741
                                            PangoFontDescription *desc,
                                            gboolean              is_default_font)
742 743 744
{
  ClutterTextPrivate *priv = self->priv;

745 746
  priv->is_default_font = is_default_font;

747 748
  if (priv->font_desc == desc ||
      pango_font_description_equal (priv->font_desc, desc))
749 750 751 752 753
    return;

  if (priv->font_desc != NULL)
    pango_font_description_free (priv->font_desc);

754
  priv->font_desc = pango_font_description_copy (desc);
755 756 757 758 759 760 761

  /* update the font name string we use */
  g_free (priv->font_name);
  priv->font_name = pango_font_description_to_string (priv->font_desc);

  clutter_text_dirty_cache (self);

762
  if (clutter_text_buffer_get_length (get_buffer (self)) != 0)
763 764
    clutter_actor_queue_relayout (CLUTTER_ACTOR (self));

765
  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_FONT_DESCRIPTION]);
766 767
}

768
static void
769
clutter_text_settings_changed_cb (ClutterText *text)
770
{
771 772 773 774 775 776 777 778 779 780 781 782
  ClutterTextPrivate *priv = text->priv;
  guint password_hint_time = 0;
  ClutterSettings *settings;

  settings = clutter_settings_get_default ();

  g_object_get (settings, "password-hint-time", &password_hint_time, NULL);

  priv->show_password_hint = password_hint_time > 0;
  priv->password_hint_timeout = password_hint_time;

  if (priv->is_default_font)
783 784
    {
      PangoFontDescription *font_desc;
785 786 787
      gchar *font_name = NULL;

      g_object_get (settings, "font-name", &font_name, NULL);
788 789 790 791 792 793

      CLUTTER_NOTE (ACTOR, "Text[%p]: default font changed to '%s'",
                    text,
                    font_name);

      font_desc = pango_font_description_from_string (font_name);
794
      clutter_text_set_font_description_internal (text, font_desc, TRUE);
795

796
      pango_font_description_free (font_desc);
797
      g_free (font_name);
798 799
    }

800 801 802 803
  clutter_text_dirty_cache (text);
  clutter_actor_queue_relayout (CLUTTER_ACTOR (text));
}

804 805 806 807 808 809 810 811 812
static void
clutter_text_direction_changed_cb (GObject    *gobject,
                                   GParamSpec *pspec)
{
  clutter_text_dirty_cache (CLUTTER_TEXT (gobject));

  /* no need to queue a relayout: set_text_direction() will do that for us */
}

813 814 815 816
/*
 * clutter_text_create_layout:
 * @text: a #ClutterText
 * @allocation_width: the allocation width
817
 * @allocation_height: the allocation height
818 819 820 821 822 823 824 825
 *
 * Like clutter_text_create_layout_no_cache(), but will also ensure
 * the glyphs cache. If a previously cached layout generated using the
 * same width is available then that will be used instead of
 * generating a new one.
 */
static PangoLayout *
clutter_text_create_layout (ClutterText *text,
826 827
                            gfloat       allocation_width,
                            gfloat       allocation_height)
828 829 830 831
{
  ClutterTextPrivate *priv = text->priv;
  LayoutCache *oldest_cache = priv->cached_layouts;
  gboolean found_free_cache = FALSE;
832 833 834
  gint width = -1;
  gint height = -1;
  PangoEllipsizeMode ellipsize = PANGO_ELLIPSIZE_NONE;
835 836
  int i;

837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
  /* First determine the width, height, and ellipsize mode that
   * we need for the layout. The ellipsize mode depends on
   * allocation_width/allocation_size as follows:
   *
   * Cases, assuming ellipsize != NONE on actor:
   *
   * Width request: ellipsization can be set or not on layout,
   * doesn't matter.
   *
   * Height request: ellipsization must never be set on layout
   * if wrap=true, because we need to measure the wrapped
   * height. It must always be set if wrap=false.
   *
   * Allocate: ellipsization must always be set.
   *
   * See http://bugzilla.gnome.org/show_bug.cgi?id=560931
   */

855
  if (priv->ellipsize != PANGO_ELLIPSIZE_NONE)
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
    {
      if (allocation_height < 0 && priv->wrap)
        ; /* must not set ellipsization on wrap=true height request */
      else
        {
          if (!priv->editable)
            ellipsize = priv->ellipsize;
        }
    }

  /* When painting, we always need to set the width, since
   * we might need to align to the right. When getting the
   * height, however, there are some cases where we know that
   * the width won't affect the width.
   *
   * - editable, single-line text actors, since those can
   *   scroll the layout.
   * - non-wrapping, non-ellipsizing actors.
   */
  if (allocation_width >= 0 &&
      (allocation_height >= 0 ||
       !((priv->editable && priv->single_line_mode) ||
         (priv->ellipsize == PANGO_ELLIPSIZE_NONE && !priv->wrap))))
    {
880
      width = allocation_width * 1024 + 0.5f;
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896
    }

  /* Pango only uses height if ellipsization is enabled, so don't set
   * height if ellipsize isn't set. Pango implicitly enables wrapping
   * if height is set, so don't set height if wrapping is disabled.
   * In other words, only set height if we want to both wrap then
   * ellipsize and we're not in single line mode.
   *
   * See http://bugzilla.gnome.org/show_bug.cgi?id=560931 if this
   * seems odd.
   */
  if (allocation_height >= 0 &&
      priv->wrap &&
      priv->ellipsize != PANGO_ELLIPSIZE_NONE &&
      !priv->single_line_mode)
    {
897
      height = allocation_height * 1024 + 0.5f;
898 899
    }

900 901 902
  /* Search for a cached layout with the same width and keep
   * track of the oldest one
   */
903 904 905 906 907 908 909 910
  for (i = 0; i < N_CACHED_LAYOUTS; i++)
    {
      if (priv->cached_layouts[i].layout == NULL)
	{
	  /* Always prefer free cache spaces */
	  found_free_cache = TRUE;
	  oldest_cache = priv->cached_layouts + i;
	}
911
      else
912
        {
913 914 915 916 917 918 919 920 921 922 923
          PangoLayout *cached = priv->cached_layouts[i].layout;
          gint cached_width = pango_layout_get_width (cached);
	  gint cached_height = pango_layout_get_height (cached);
	  gint cached_ellipsize = pango_layout_get_ellipsize (cached);

	  if (cached_width == width &&
	      cached_height == height &&
	      cached_ellipsize == ellipsize)
	    {
              /* If this cached layout is using the same size then we can
               * just return that directly
924
               */
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
              CLUTTER_NOTE (ACTOR,
                            "ClutterText: %p: cache hit for size %.2fx%.2f",
                            text,
                            allocation_width,
                            allocation_height);

              return priv->cached_layouts[i].layout;
	    }

	  /* When getting the preferred height for a specific width,
	   * we might be able to reuse the layout from getting the
	   * preferred width. If the width that the layout gives
	   * unconstrained is less than the width that we are using
	   * than the height will be unaffected by that width.
	   */
	  if (allocation_height < 0 &&
              cached_width == -1 &&
              cached_ellipsize == ellipsize)
	    {
	      PangoRectangle logical_rect;

	      pango_layout_get_extents (priv->cached_layouts[i].layout,
                                        NULL,
                                        &logical_rect);

	      if (logical_rect.width <= width)
		{
		  /* We've been asked for our height for the width we gave as a result
		   * of a _get_preferred_width call
		   */
		  CLUTTER_NOTE (ACTOR,
                                "ClutterText: %p: cache hit for size %.2fx%.2f "
				"(unwrapped width narrower than given width)",
				text,
				allocation_width,
				allocation_height);

		  return priv->cached_layouts[i].layout;
		}
	    }

	  if (!found_free_cache &&
	      (priv->cached_layouts[i].age < oldest_cache->age))
	    {
	      oldest_cache = priv->cached_layouts + i;
	    }
971 972 973
        }
    }

974
  CLUTTER_NOTE (ACTOR, "ClutterText: %p: cache miss for size %.2fx%.2f",
975
		text,
976 977
                allocation_width,
                allocation_height);
978 979 980 981 982 983 984

  /* If we make it here then we didn't have a cached version so we
     need to recreate the layout */
  if (oldest_cache->layout)
    g_object_unref (oldest_cache->layout);

  oldest_cache->layout =
985
    clutter_text_create_layout_no_cache (text, width, height, ellipsize);
986 987 988 989 990 991 992

  cogl_pango_ensure_glyph_cache_for_layout (oldest_cache->layout);

  /* Mark the 'time' this cache was created and advance the time */
  oldest_cache->age = priv->cache_age++;
  return oldest_cache->layout;
}
993

994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
/**
 * clutter_text_coords_to_position:
 * @self: a #ClutterText
 * @x: the X coordinate, relative to the actor
 * @y: the Y coordinate, relative to the actor
 *
 * Retrieves the position of the character at the given coordinates.
 *
 * Return: the position of the character
 *
 * Since: 1.10
 */
gint
clutter_text_coords_to_position (ClutterText *self,
1008 1009
                                 gfloat       x,
                                 gfloat       y)
1010 1011 1012 1013 1014
{
  gint index_;
  gint px, py;
  gint trailing;

1015
  g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0);
1016

1017 1018 1019 1020 1021
  /* Take any offset due to scrolling into account, and normalize
   * the coordinates to PangoScale units
   */
  px = (x - self->priv->text_x) * PANGO_SCALE;
  py = (y - self->priv->text_y) * PANGO_SCALE;
1022

1023
  pango_layout_xy_to_index (clutter_text_get_layout (self),
1024 1025
                            px, py,
                            &index_, &trailing);
1026 1027 1028 1029

  return index_ + trailing;
}

1030
/**
1031 1032 1033
 * clutter_text_position_to_coords:
 * @self: a #ClutterText
 * @position: position in characters
1034 1035 1036
 * @x: (out): return location for the X coordinate, or %NULL
 * @y: (out): return location for the Y coordinate, or %NULL
 * @line_height: (out): return location for the line height, or %NULL
1037 1038 1039 1040
 *
 * Retrieves the coordinates of the given @position.
 *
 * Return value: %TRUE if the conversion was successful
1041 1042
 *
 * Since: 1.0
1043
 */
1044
gboolean
1045 1046
clutter_text_position_to_coords (ClutterText *self,
                                 gint         position,
1047 1048 1049
                                 gfloat      *x,
                                 gfloat      *y,
                                 gfloat      *line_height)
1050
{
1051
  ClutterTextPrivate *priv;
1052
  PangoRectangle rect;
1053
  gint n_chars;
1054
  gint password_char_bytes = 1;
1055
  gint index_;
1056
  gsize n_bytes;
1057

1058 1059 1060 1061
  g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);

  priv = self->priv;

1062
  n_chars = clutter_text_buffer_get_length (get_buffer (self));
1063
  if (priv->preedit_set)
1064
    n_chars += priv->preedit_n_chars;
1065 1066

  if (position < -1 || position > n_chars)
1067 1068
    return FALSE;

1069 1070
  if (priv->password_char != 0)
    password_char_bytes = g_unichar_to_utf8 (priv->password_char, NULL);
1071 1072

  if (position == -1)
1073
    {
1074
      if (priv->password_char == 0)
1075
        {
1076
          n_bytes = clutter_text_buffer_get_bytes (get_buffer (self));
1077
          if (priv->editable && priv->preedit_set)
1078
            index_ = n_bytes + strlen (priv->preedit_str);
1079
          else
1080
            index_ = n_bytes;
1081
        }
1082
      else
1083
        index_ = n_chars * password_char_bytes;
1084
    }
1085 1086 1087 1088
  else if (position == 0)
    {
      index_ = 0;
    }
1089
  else
1090
    {
1091
      gchar *text = clutter_text_get_display_text (self);
1092 1093 1094 1095
      GString *tmp = g_string_new (text);
      gint cursor_index;

      cursor_index = offset_to_bytes (text, priv->position);
1096 1097 1098

      if (priv->preedit_str != NULL)
        g_string_insert (tmp, cursor_index, priv->preedit_str);
1099

1100
      if (priv->password_char == 0)
1101
        index_ = offset_to_bytes (tmp->str, position);
1102
      else
1103
        index_ = position * password_char_bytes;
1104 1105

      g_free (text);
1106
      g_string_free (tmp, TRUE);
1107
    }
1108

1109 1110
  pango_layout_get_cursor_pos (clutter_text_get_layout (self),
                               index_,
1111
                               &rect, NULL);
1112 1113

  if (x)
1114
    {
1115
      *x = (gfloat) rect.x / 1024.0f;
1116 1117 1118 1119 1120

      /* Take any offset due to scrolling into account */
      if (priv->single_line_mode)
        *x += priv->text_x;
    }
1121

1122
  if (y)
1123
    *y = (gfloat) rect.y / 1024.0f;
1124

1125
  if (line_height)
1126
    *line_height = (gfloat) rect.height / 1024.0f;
1127

1128
  return TRUE;
1129 1130
}

1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
static inline void
update_cursor_location (ClutterText *self)
{
  ClutterTextPrivate *priv = self->priv;
  ClutterRect rect;
  float x, y;

  if (!priv->editable)
    return;

  rect = priv->cursor_rect;
  clutter_actor_get_transformed_position (CLUTTER_ACTOR (self), &x, &y);
  clutter_rect_offset (&rect, x, y);
  clutter_input_focus_set_cursor_location (priv->input_focus, &rect);
}

1147 1148
static inline void
clutter_text_ensure_cursor_position (ClutterText *self)
1149
{
1150
  ClutterTextPrivate *priv = self->priv;
1151
  gfloat x, y, cursor_height;
1152
  ClutterRect cursor_rect = CLUTTER_RECT_INIT_ZERO;
1153 1154 1155 1156
  gint position;

  position = priv->position;

1157
  if (priv->editable && priv->preedit_set)
1158 1159
    {
      if (position == -1)
1160
        position = clutter_text_buffer_get_length (get_buffer (self));
1161

1162 1163
      position += priv->preedit_cursor_pos;
    }
1164

1165 1166 1167 1168
  CLUTTER_NOTE (MISC, "Cursor at %d (preedit %s at pos: %d)",
                position,
                priv->preedit_set ? "set" : "unset",
                priv->preedit_set ? priv->preedit_cursor_pos : 0);
1169

1170
  x = y = cursor_height = 0;
1171
  clutter_text_position_to_coords (self, position,
1172 1173
                                   &x, &y,
                                   &cursor_height);
1174

1175 1176 1177 1178 1179
  clutter_rect_init (&cursor_rect,
                     x,
                     y + CURSOR_Y_PADDING,
                     priv->cursor_size,
                     cursor_height - 2 * CURSOR_Y_PADDING);
1180

1181
  if (!clutter_rect_equals (&priv->cursor_rect, &cursor_rect))
1182
    {
1183
      ClutterGeometry cursor_pos;
1184

1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
      priv->cursor_rect = cursor_rect;

      /* XXX:2.0 - remove */
      cursor_pos.x = clutter_rect_get_x (&priv->cursor_rect);
      cursor_pos.y = clutter_rect_get_y (&priv->cursor_rect);
      cursor_pos.width = clutter_rect_get_width (&priv->cursor_rect);
      cursor_pos.height = clutter_rect_get_height (&priv->cursor_rect);
      g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &cursor_pos);

      g_signal_emit (self, text_signals[CURSOR_CHANGED], 0);
1195 1196

      update_cursor_location (self);
1197
    }
1198 1199
}

1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
/**
 * clutter_text_delete_selection:
 * @self: a #ClutterText
 *
 * Deletes the currently selected text
 *
 * This function is only useful in subclasses of #ClutterText
 *
 * Return value: %TRUE if text was deleted or if the text actor
 *   is empty, and %FALSE otherwise
 *
 * Since: 1.0
 */
gboolean
clutter_text_delete_selection (ClutterText *self)
1215
{
1216
  ClutterTextPrivate *priv;
1217 1218
  gint start_index;
  gint end_index;
1219
  gint old_position, old_selection;
1220
  guint n_chars;
1221

1222 1223 1224 1225
  g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);

  priv = self->priv;

1226 1227
  n_chars = clutter_text_buffer_get_length (get_buffer (self));
  if (n_chars == 0)
1228 1229
    return TRUE;

1230 1231
  start_index = priv->position == -1 ? n_chars : priv->position;
  end_index = priv->selection_bound == -1 ? n_chars : priv->selection_bound;
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242

  if (end_index == start_index)
    return FALSE;

  if (end_index < start_index)
    {
      gint temp = start_index;
      start_index = end_index;
      end_index = temp;
    }

1243 1244 1245
  old_position = priv->position;
  old_selection = priv->selection_bound;

1246 1247
  clutter_text_delete_text (self, start_index, end_index);

1248 1249
  priv->position = start_index;
  priv->selection_bound = start_index;
1250

1251
  /* Not required to be guarded by g_object_freeze/thaw_notify */
1252
  if (priv->position != old_position)
1253 1254 1255 1256 1257
    {
      /* XXX:2.0 - remove */
      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]);
      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_POSITION]);
    }
1258 1259

  if (priv->selection_bound != old_selection)
1260
    g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
1261

1262 1263 1264
  return TRUE;
}

1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
/*
 * Utility function to update both cursor position and selection bound
 * at once
 */
static inline void
clutter_text_set_positions (ClutterText *self,
                            gint         new_pos,
                            gint         new_bound)
{
  g_object_freeze_notify (G_OBJECT (self));
  clutter_text_set_cursor_position (self, new_pos);
  clutter_text_set_selection_bound (self, new_bound);
  g_object_thaw_notify (G_OBJECT (self));
}

1280 1281 1282 1283 1284 1285 1286 1287 1288 1289
static inline void
clutter_text_set_markup_internal (ClutterText *self,
                                  const gchar *str)
{
  ClutterTextPrivate *priv = self->priv;
  GError *error;
  gchar *text = NULL;
  PangoAttrList *attrs = NULL;
  gboolean res;

1290 1291
  g_assert (str != NULL);

1292 1293 1294 1295 1296 1297
  error = NULL;
  res = pango_parse_markup (str, -1, 0,
                            &attrs,
                            &text,
                            NULL,
                            &error);
1298

1299 1300
  if (!res)
    {
1301
      if (G_LIKELY (error != NULL))
1302
        {
1303 1304
          g_warning ("Failed to set the markup of the actor '%s': %s",
                     _clutter_actor_get_debug_name (CLUTTER_ACTOR (self)),
1305 1306 1307 1308
                     error->message);
          g_error_free (error);
        }
      else
1309 1310
        g_warning ("Failed to set the markup of the actor '%s'",
                   _clutter_actor_get_debug_name (CLUTTER_ACTOR (self)));
1311 1312 1313 1314

      return;
    }

1315 1316
  if (text)
    {
1317
      clutter_text_buffer_set_text (get_buffer (self), text, -1);
1318 1319
      g_free (text);
    }
1320

1321
  /* Store the new markup attributes */
1322
  if (priv->markup_attrs != NULL)
1323
    pango_attr_list_unref (priv->markup_attrs);
1324

1325
  priv->markup_attrs = attrs;
1326

1327 1328
  /* Clear the effective attributes so they will be regenerated when a
     layout is created */
1329
  if (priv->effective_attrs != NULL)
1330 1331 1332
    {
      pango_attr_list_unref (priv->effective_attrs);
      priv->effective_attrs = NULL;
1333 1334 1335
    }
}

1336 1337 1338 1339 1340
static void
clutter_text_set_property (GObject      *gobject,
                           guint         prop_id,
                           const GValue *value,
                           GParamSpec   *pspec)
1341
{
1342
  ClutterText *self = CLUTTER_TEXT (gobject);
1343

1344
  switch (prop_id)
1345
    {
1346 1347 1348 1349
    case PROP_BUFFER:
      clutter_text_set_buffer (self, g_value_get_object (value));
      break;

1350
    case PROP_TEXT:
1351 1352 1353
      {
        const char *str = g_value_get_string (value);
        if (self->priv->use_markup)
1354
          clutter_text_set_markup_internal (self, str ? str : "");
1355
        else
1356
          clutter_text_buffer_set_text (get_buffer (self), str ? str : "", -1);
1357
      }
1358
      break;
1359

1360 1361 1362
    case PROP_COLOR:
      clutter_text_set_color (self, clutter_value_get_color (value));
      break;
1363

1364 1365 1366
    case PROP_FONT_NAME:
      clutter_text_set_font_name (self, g_value_get_string (value));
      break;
1367

1368 1369 1370 1371
    case PROP_FONT_DESCRIPTION:
      clutter_text_set_font_description (self, g_value_get_boxed (value));
      break;

1372 1373 1374
    case PROP_USE_MARKUP:
      clutter_text_set_use_markup (self, g_value_get_boolean (value));
      break;
1375

1376 1377 1378
    case PROP_ATTRIBUTES:
      clutter_text_set_attributes (self, g_value_get_boxed (value));
      break;
1379

1380 1381
    case PROP_LINE_ALIGNMENT:
      clutter_text_set_line_alignment (self, g_value_get_enum (value));
1382
      break;
1383

1384 1385 1386
    case PROP_LINE_WRAP:
      clutter_text_set_line_wrap (self, g_value_get_boolean (value));
      break;
1387

1388 1389
    case PROP_LINE_WRAP_MODE:
      clutter_text_set_line_wrap_mode (self, g_value_get_enum (value));
1390
      break;
1391 1392 1393

    case PROP_JUSTIFY:
      clutter_text_set_justify (self, g_value_get_boolean (value));
1394
      break;
1395 1396 1397

    case PROP_ELLIPSIZE:
      clutter_text_set_ellipsize (self, g_value_get_enum (value));
1398
      break;
1399

1400 1401
    case PROP_POSITION: /* XXX:2.0: remove */
    case PROP_CURSOR_POSITION:
1402
      clutter_text_set_cursor_position (self, g_value_get_int (value));
1403
      break;
1404

1405
    case PROP_SELECTION_BOUND:
1406
      clutter_text_set_selection_bound (self, g_value_get_int (value));
1407
      break;
1408

1409 1410 1411 1412
    case PROP_SELECTION_COLOR:
      clutter_text_set_selection_color (self, g_value_get_boxed (value));
      break;

1413
    case PROP_CURSOR_VISIBLE:
1414
      clutter_text_set_cursor_visible (self, g_value_get_boolean (value));
1415
      break;
1416