[clutter] text: Implement ClutterTextBuffer



commit c073764369ee5969aa40ab90bcbbd1d9ffb22518
Author: Stef Walter <stefw collabora co uk>
Date:   Wed Jun 15 10:06:31 2011 +0100

    text: Implement ClutterTextBuffer
    
     * Abstracts the buffer for text in ClutterText
     * Allows implementation of undo/redo.
     * Allows use of non-pageable memory for text
       in the case of sensitive passwords.
     * Implement a test with two ClutterText using the same
       buffer.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=652653

 clutter/Makefile.am           |    6 +-
 clutter/clutter-marshal.list  |    2 +
 clutter/clutter-text-buffer.c |  764 +++++++++++++++++++++++++++++++++++++++++
 clutter/clutter-text-buffer.h |  139 ++++++++
 clutter/clutter-text.c        |  693 ++++++++++++++++++++-----------------
 clutter/clutter-text.h        |    6 +-
 tests/interactive/test-text.c |   24 ++-
 7 files changed, 1304 insertions(+), 330 deletions(-)
---
diff --git a/clutter/Makefile.am b/clutter/Makefile.am
index 0dfe657..1bb9adc 100644
--- a/clutter/Makefile.am
+++ b/clutter/Makefile.am
@@ -112,7 +112,8 @@ source_h =					\
 	$(srcdir)/clutter-state.h		\
 	$(srcdir)/clutter-table-layout.h	\
 	$(srcdir)/clutter-texture.h 		\
-        $(srcdir)/clutter-text.h                \
+	$(srcdir)/clutter-text.h			\
+	$(srcdir)/clutter-text-buffer.h		\
 	$(srcdir)/clutter-timeline.h 		\
 	$(srcdir)/clutter-types.h		\
 	$(srcdir)/clutter-units.h 		\
@@ -184,7 +185,8 @@ source_c = \
 	$(srcdir)/clutter-state.c		\
 	$(srcdir)/clutter-table-layout.c	\
 	$(srcdir)/clutter-texture.c 		\
-	$(srcdir)/clutter-text.c                \
+	$(srcdir)/clutter-text.c			\
+	$(srcdir)/clutter-text-buffer.c		\
 	$(srcdir)/clutter-timeline.c 		\
 	$(srcdir)/clutter-units.c		\
 	$(srcdir)/clutter-util.c 		\
diff --git a/clutter/clutter-marshal.list b/clutter/clutter-marshal.list
index d189a82..8f9e0f8 100644
--- a/clutter/clutter-marshal.list
+++ b/clutter/clutter-marshal.list
@@ -24,5 +24,7 @@ VOID:POINTER
 VOID:STRING,BOOLEAN,BOOLEAN
 VOID:STRING,INT
 VOID:UINT
+VOID:UINT,STRING,UINT
+VOID:UINT,UINT
 VOID:VOID
 VOID:STRING,INT,POINTER
diff --git a/clutter/clutter-text-buffer.c b/clutter/clutter-text-buffer.c
new file mode 100644
index 0000000..62d6bd7
--- /dev/null
+++ b/clutter/clutter-text-buffer.c
@@ -0,0 +1,764 @@
+/* clutter-text-buffer.c
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "clutter-text-buffer.h"
+#include "clutter-marshal.h"
+#include "clutter-private.h"
+
+#include <string.h>
+
+/**
+ * SECTION:clutter-text-buffer
+ * @title: ClutterTextBuffer
+ * @short_description: Text buffer for ClutterText
+ *
+ * The #ClutterTextBuffer class contains the actual text displayed in a
+ * #ClutterText widget.
+ *
+ * A single #ClutterTextBuffer object can be shared by multiple #ClutterText
+ * widgets which will then share the same text content, but not the cursor
+ * position, visibility attributes, icon etc.
+ *
+ * #ClutterTextBuffer may be derived from. Such a derived class might allow
+ * text to be stored in an alternate location, such as non-pageable memory,
+ * useful in the case of important passwords. Or a derived class could
+ * integrate with an application's concept of undo/redo.
+ *
+ * Since: 1.8
+ */
+
+/* Initial size of buffer, in bytes */
+#define MIN_SIZE 16
+
+enum {
+  PROP_0,
+  PROP_TEXT,
+  PROP_LENGTH,
+  PROP_MAX_LENGTH,
+  PROP_LAST
+};
+
+static GParamSpec *obj_props[PROP_LAST] = { NULL, };
+
+enum {
+  INSERTED_TEXT,
+  DELETED_TEXT,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _ClutterTextBufferPrivate
+{
+  gint  max_length;
+
+  /* Only valid if this class is not derived */
+  gchar *normal_text;
+  gsize  normal_text_size;
+  gsize  normal_text_bytes;
+  guint  normal_text_chars;
+};
+
+G_DEFINE_TYPE (ClutterTextBuffer, clutter_text_buffer, G_TYPE_OBJECT);
+
+/* --------------------------------------------------------------------------------
+ * DEFAULT IMPLEMENTATIONS OF TEXT BUFFER
+ *
+ * These may be overridden by a derived class, behavior may be changed etc...
+ * The normal_text and normal_text_xxxx fields may not be valid when
+ * this class is derived from.
+ */
+
+/* Overwrite a memory that might contain sensitive information. */
+static void
+trash_area (gchar *area,
+            gsize  len)
+{
+  volatile gchar *varea = (volatile gchar *)area;
+  while (len-- > 0)
+    *varea++ = 0;
+}
+
+static const gchar*
+clutter_text_buffer_normal_get_text (ClutterTextBuffer *buffer,
+                                  gsize          *n_bytes)
+{
+  if (n_bytes)
+    *n_bytes = buffer->priv->normal_text_bytes;
+  if (!buffer->priv->normal_text)
+      return "";
+  return buffer->priv->normal_text;
+}
+
+static guint
+clutter_text_buffer_normal_get_length (ClutterTextBuffer *buffer)
+{
+  return buffer->priv->normal_text_chars;
+}
+
+static guint
+clutter_text_buffer_normal_insert_text (ClutterTextBuffer *buffer,
+                                     guint           position,
+                                     const gchar    *chars,
+                                     guint           n_chars)
+{
+  ClutterTextBufferPrivate *pv = buffer->priv;
+  gsize prev_size;
+  gsize n_bytes;
+  gsize at;
+
+  n_bytes = g_utf8_offset_to_pointer (chars, n_chars) - chars;
+
+  /* Need more memory */
+  if (n_bytes + pv->normal_text_bytes + 1 > pv->normal_text_size)
+    {
+      gchar *et_new;
+
+      prev_size = pv->normal_text_size;
+
+      /* Calculate our new buffer size */
+      while (n_bytes + pv->normal_text_bytes + 1 > pv->normal_text_size)
+        {
+          if (pv->normal_text_size == 0)
+            pv->normal_text_size = MIN_SIZE;
+          else
+            {
+              if (2 * pv->normal_text_size < CLUTTER_TEXT_BUFFER_MAX_SIZE)
+                pv->normal_text_size *= 2;
+              else
+                {
+                  pv->normal_text_size = CLUTTER_TEXT_BUFFER_MAX_SIZE;
+                  if (n_bytes > pv->normal_text_size - pv->normal_text_bytes - 1)
+                    {
+                      n_bytes = pv->normal_text_size - pv->normal_text_bytes - 1;
+                      n_bytes = g_utf8_find_prev_char (chars, chars + n_bytes + 1) - chars;
+                      n_chars = g_utf8_strlen (chars, n_bytes);
+                    }
+                  break;
+                }
+            }
+        }
+
+      /* Could be a password, so can't leave stuff in memory. */
+      et_new = g_malloc (pv->normal_text_size);
+      memcpy (et_new, pv->normal_text, MIN (prev_size, pv->normal_text_size));
+      trash_area (pv->normal_text, prev_size);
+      g_free (pv->normal_text);
+      pv->normal_text = et_new;
+    }
+
+  /* Actual text insertion */
+  at = g_utf8_offset_to_pointer (pv->normal_text, position) - pv->normal_text;
+  g_memmove (pv->normal_text + at + n_bytes, pv->normal_text + at, pv->normal_text_bytes - at);
+  memcpy (pv->normal_text + at, chars, n_bytes);
+
+  /* Book keeping */
+  pv->normal_text_bytes += n_bytes;
+  pv->normal_text_chars += n_chars;
+  pv->normal_text[pv->normal_text_bytes] = '\0';
+
+  clutter_text_buffer_emit_inserted_text (buffer, position, chars, n_chars);
+  return n_chars;
+}
+
+static guint
+clutter_text_buffer_normal_delete_text (ClutterTextBuffer *buffer,
+                                     guint           position,
+                                     guint           n_chars)
+{
+  ClutterTextBufferPrivate *pv = buffer->priv;
+  gsize start, end;
+
+  if (position > pv->normal_text_chars)
+    position = pv->normal_text_chars;
+  if (position + n_chars > pv->normal_text_chars)
+    n_chars = pv->normal_text_chars - position;
+
+  if (n_chars > 0)
+    {
+      start = g_utf8_offset_to_pointer (pv->normal_text, position) - pv->normal_text;
+      end = g_utf8_offset_to_pointer (pv->normal_text, position + n_chars) - pv->normal_text;
+
+      g_memmove (pv->normal_text + start, pv->normal_text + end, pv->normal_text_bytes + 1 - end);
+      pv->normal_text_chars -= n_chars;
+      pv->normal_text_bytes -= (end - start);
+
+      /*
+       * Could be a password, make sure we don't leave anything sensitive after
+       * the terminating zero.  Note, that the terminating zero already trashed
+       * one byte.
+       */
+      trash_area (pv->normal_text + pv->normal_text_bytes + 1, end - start - 1);
+
+      clutter_text_buffer_emit_deleted_text (buffer, position, n_chars);
+    }
+
+  return n_chars;
+}
+
+/* --------------------------------------------------------------------------------
+ *
+ */
+
+static void
+clutter_text_buffer_real_inserted_text (ClutterTextBuffer *buffer,
+                                     guint           position,
+                                     const gchar    *chars,
+                                     guint           n_chars)
+{
+  g_object_notify (G_OBJECT (buffer), "text");
+  g_object_notify (G_OBJECT (buffer), "length");
+}
+
+static void
+clutter_text_buffer_real_deleted_text (ClutterTextBuffer *buffer,
+                                    guint           position,
+                                    guint           n_chars)
+{
+  g_object_notify (G_OBJECT (buffer), "text");
+  g_object_notify (G_OBJECT (buffer), "length");
+}
+
+/* --------------------------------------------------------------------------------
+ *
+ */
+
+static void
+clutter_text_buffer_init (ClutterTextBuffer *buffer)
+{
+  ClutterTextBufferPrivate *pv;
+
+  pv = buffer->priv = G_TYPE_INSTANCE_GET_PRIVATE (buffer, CLUTTER_TYPE_TEXT_BUFFER, ClutterTextBufferPrivate);
+
+  pv->normal_text = NULL;
+  pv->normal_text_chars = 0;
+  pv->normal_text_bytes = 0;
+  pv->normal_text_size = 0;
+}
+
+static void
+clutter_text_buffer_finalize (GObject *obj)
+{
+  ClutterTextBuffer *buffer = CLUTTER_TEXT_BUFFER (obj);
+  ClutterTextBufferPrivate *pv = buffer->priv;
+
+  if (pv->normal_text)
+    {
+      trash_area (pv->normal_text, pv->normal_text_size);
+      g_free (pv->normal_text);
+      pv->normal_text = NULL;
+      pv->normal_text_bytes = pv->normal_text_size = 0;
+      pv->normal_text_chars = 0;
+    }
+
+  G_OBJECT_CLASS (clutter_text_buffer_parent_class)->finalize (obj);
+}
+
+static void
+clutter_text_buffer_set_property (GObject      *obj,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  ClutterTextBuffer *buffer = CLUTTER_TEXT_BUFFER (obj);
+
+  switch (prop_id)
+    {
+    case PROP_MAX_LENGTH:
+      clutter_text_buffer_set_max_length (buffer, g_value_get_int (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_text_buffer_get_property (GObject    *obj,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  ClutterTextBuffer *buffer = CLUTTER_TEXT_BUFFER (obj);
+
+  switch (prop_id)
+    {
+    case PROP_TEXT:
+      g_value_set_string (value, clutter_text_buffer_get_text (buffer));
+      break;
+    case PROP_LENGTH:
+      g_value_set_uint (value, clutter_text_buffer_get_length (buffer));
+      break;
+    case PROP_MAX_LENGTH:
+      g_value_set_int (value, clutter_text_buffer_get_max_length (buffer));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_text_buffer_class_init (ClutterTextBufferClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = clutter_text_buffer_finalize;
+  gobject_class->set_property = clutter_text_buffer_set_property;
+  gobject_class->get_property = clutter_text_buffer_get_property;
+
+  klass->get_text = clutter_text_buffer_normal_get_text;
+  klass->get_length = clutter_text_buffer_normal_get_length;
+  klass->insert_text = clutter_text_buffer_normal_insert_text;
+  klass->delete_text = clutter_text_buffer_normal_delete_text;
+
+  klass->inserted_text = clutter_text_buffer_real_inserted_text;
+  klass->deleted_text = clutter_text_buffer_real_deleted_text;
+
+  g_type_class_add_private (gobject_class, sizeof (ClutterTextBufferPrivate));
+
+  /**
+   * ClutterTextBuffer:text:
+   *
+   * The contents of the buffer.
+   *
+   * Since: 1.8
+   */
+  obj_props[PROP_TEXT] =
+      g_param_spec_string ("text",
+                           P_("Text"),
+                           P_("The contents of the buffer"),
+                           "",
+                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * ClutterTextBuffer:length:
+   *
+   * The length (in characters) of the text in buffer.
+   *
+   * Since: 1.8
+   */
+  obj_props[PROP_LENGTH] =
+      g_param_spec_uint ("length",
+                         P_("Text length"),
+                         P_("Length of the text currently in the buffer"),
+                         0, CLUTTER_TEXT_BUFFER_MAX_SIZE, 0,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * ClutterTextBuffer:max-length:
+   *
+   * The maximum length (in characters) of the text in the buffer.
+   *
+   * Since: 1.8
+   */
+  obj_props[PROP_MAX_LENGTH] =
+      g_param_spec_int ("max-length",
+                        P_("Maximum length"),
+                        P_("Maximum number of characters for this entry. Zero if no maximum"),
+                        0, CLUTTER_TEXT_BUFFER_MAX_SIZE, 0,
+                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
+
+  /**
+   * ClutterTextBuffer::inserted-text:
+   * @buffer: a #ClutterTextBuffer
+   * @position: the position the text was inserted at.
+   * @chars: The text that was inserted.
+   * @n_chars: The number of characters that were inserted.
+   *
+   * This signal is emitted after text is inserted into the buffer.
+   *
+   * Since: 1.8
+   */
+  signals[INSERTED_TEXT] = g_signal_new (I_("inserted-text"),
+                                         CLUTTER_TYPE_TEXT_BUFFER,
+                                         G_SIGNAL_RUN_FIRST,
+                                         G_STRUCT_OFFSET (ClutterTextBufferClass, inserted_text),
+                                         NULL, NULL,
+                                         _clutter_marshal_VOID__UINT_STRING_UINT,
+                                         G_TYPE_NONE, 3,
+                                         G_TYPE_UINT,
+                                         G_TYPE_STRING,
+                                         G_TYPE_UINT);
+
+  /**
+   * ClutterTextBuffer::deleted-text:
+   * @buffer: a #ClutterTextBuffer
+   * @position: the position the text was deleted at.
+   * @n_chars: The number of characters that were deleted.
+   *
+   * This signal is emitted after text is deleted from the buffer.
+   *
+   * Since: 1.8
+   */
+  signals[DELETED_TEXT] =  g_signal_new (I_("deleted-text"),
+                                         CLUTTER_TYPE_TEXT_BUFFER,
+                                         G_SIGNAL_RUN_FIRST,
+                                         G_STRUCT_OFFSET (ClutterTextBufferClass, deleted_text),
+                                         NULL, NULL,
+                                         _clutter_marshal_VOID__UINT_UINT,
+                                         G_TYPE_NONE, 2,
+                                         G_TYPE_UINT,
+                                         G_TYPE_UINT);
+}
+
+/* --------------------------------------------------------------------------------
+ *
+ */
+
+/**
+ * clutter_text_buffer_new:
+ *
+ * Create a new ClutterTextBuffer object.
+ *
+ * Return value: A new ClutterTextBuffer object.
+ *
+ * Since: 1.8
+ **/
+ClutterTextBuffer*
+clutter_text_buffer_new (void)
+{
+  return g_object_new (CLUTTER_TYPE_TEXT_BUFFER, NULL);
+}
+
+
+/**
+ * clutter_text_buffer_new_with_text:
+ * @text: (allow-none): initial buffer text
+ * @text_len: initial buffer text length, or -1 for null-terminated.
+ *
+ * Create a new ClutterTextBuffer object with some text.
+ *
+ * Return value: A new ClutterTextBuffer object.
+ *
+ * Since: 1.8
+ **/
+ClutterTextBuffer*
+clutter_text_buffer_new_with_text (const gchar   *text,
+                                   gssize         text_len)
+{
+  ClutterTextBuffer *buffer;
+  buffer = clutter_text_buffer_new ();
+  clutter_text_buffer_set_text (buffer, text, text_len);
+  return buffer;
+}
+
+
+/**
+ * clutter_text_buffer_get_length:
+ * @buffer: a #ClutterTextBuffer
+ *
+ * Retrieves the length in characters of the buffer.
+ *
+ * Return value: The number of characters in the buffer.
+ *
+ * Since: 1.8
+ **/
+guint
+clutter_text_buffer_get_length (ClutterTextBuffer *buffer)
+{
+  ClutterTextBufferClass *klass;
+
+  g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
+
+  klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
+  g_return_val_if_fail (klass->get_length != NULL, 0);
+
+  return (*klass->get_length) (buffer);
+}
+
+/**
+ * clutter_text_buffer_get_bytes:
+ * @buffer: a #ClutterTextBuffer
+ *
+ * Retrieves the length in bytes of the buffer.
+ * See clutter_text_buffer_get_length().
+ *
+ * Return value: The byte length of the buffer.
+ *
+ * Since: 1.8
+ **/
+gsize
+clutter_text_buffer_get_bytes (ClutterTextBuffer *buffer)
+{
+  ClutterTextBufferClass *klass;
+  gsize bytes = 0;
+
+  g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
+
+  klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
+  g_return_val_if_fail (klass->get_text != NULL, 0);
+
+  (*klass->get_text) (buffer, &bytes);
+  return bytes;
+}
+
+/**
+ * clutter_text_buffer_get_text:
+ * @buffer: a #ClutterTextBuffer
+ *
+ * Retrieves the contents of the buffer.
+ *
+ * The memory pointer returned by this call will not change
+ * unless this object emits a signal, or is finalized.
+ *
+ * Return value: a pointer to the contents of the widget as a
+ *      string. This string points to internally allocated
+ *      storage in the buffer and must not be freed, modified or
+ *      stored.
+ *
+ * Since: 1.8
+ **/
+const gchar*
+clutter_text_buffer_get_text (ClutterTextBuffer *buffer)
+{
+  ClutterTextBufferClass *klass;
+
+  g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), NULL);
+
+  klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
+  g_return_val_if_fail (klass->get_text != NULL, NULL);
+
+  return (*klass->get_text) (buffer, NULL);
+}
+
+/**
+ * clutter_text_buffer_set_text:
+ * @buffer: a #ClutterTextBuffer
+ * @chars: the new text
+ * @n_chars: the number of characters in @text, or -1
+ *
+ * Sets the text in the buffer.
+ *
+ * This is roughly equivalent to calling clutter_text_buffer_delete_text()
+ * and clutter_text_buffer_insert_text().
+ *
+ * Note that @n_chars is in characters, not in bytes.
+ *
+ * Since: 1.8
+ **/
+void
+clutter_text_buffer_set_text (ClutterTextBuffer *buffer,
+                              const gchar       *chars,
+                              gint               n_chars)
+{
+  g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (chars != NULL);
+
+  g_object_freeze_notify (G_OBJECT (buffer));
+  clutter_text_buffer_delete_text (buffer, 0, -1);
+  clutter_text_buffer_insert_text (buffer, 0, chars, n_chars);
+  g_object_thaw_notify (G_OBJECT (buffer));
+}
+
+/**
+ * clutter_text_buffer_set_max_length:
+ * @buffer: a #ClutterTextBuffer
+ * @max_length: the maximum length of the entry buffer, or 0 for no maximum.
+ *   (other than the maximum length of entries.) The value passed in will
+ *   be clamped to the range 0-65536.
+ *
+ * Sets the maximum allowed length of the contents of the buffer. If
+ * the current contents are longer than the given length, then they
+ * will be truncated to fit.
+ *
+ * Since: 1.8
+ **/
+void
+clutter_text_buffer_set_max_length (ClutterTextBuffer *buffer,
+                                    gint               max_length)
+{
+  g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
+
+  max_length = CLAMP (max_length, 0, CLUTTER_TEXT_BUFFER_MAX_SIZE);
+
+  if (max_length > 0 && clutter_text_buffer_get_length (buffer) > max_length)
+    clutter_text_buffer_delete_text (buffer, max_length, -1);
+
+  buffer->priv->max_length = max_length;
+  g_object_notify (G_OBJECT (buffer), "max-length");
+}
+
+/**
+ * clutter_text_buffer_get_max_length:
+ * @buffer: a #ClutterTextBuffer
+ *
+ * Retrieves the maximum allowed length of the text in
+ * @buffer. See clutter_text_buffer_set_max_length().
+ *
+ * Return value: the maximum allowed number of characters
+ *               in #ClutterTextBuffer, or 0 if there is no maximum.
+ *
+ * Since: 1.8
+ */
+gint
+clutter_text_buffer_get_max_length (ClutterTextBuffer *buffer)
+{
+  g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
+  return buffer->priv->max_length;
+}
+
+/**
+ * clutter_text_buffer_insert_text:
+ * @buffer: a #ClutterTextBuffer
+ * @position: the position at which to insert text.
+ * @chars: the text to insert into the buffer.
+ * @n_chars: the length of the text in characters, or -1
+ *
+ * Inserts @n_chars characters of @chars into the contents of the
+ * buffer, at position @position.
+ *
+ * If @n_chars is negative, then characters from chars will be inserted
+ * until a null-terminator is found. If @position or @n_chars are out of
+ * bounds, or the maximum buffer text length is exceeded, then they are
+ * coerced to sane values.
+ *
+ * Note that the position and length are in characters, not in bytes.
+ *
+ * Returns: The number of characters actually inserted.
+ *
+ * Since: 1.8
+ */
+guint
+clutter_text_buffer_insert_text (ClutterTextBuffer *buffer,
+                                 guint              position,
+                                 const gchar       *chars,
+                                 gint               n_chars)
+{
+  ClutterTextBufferClass *klass;
+  ClutterTextBufferPrivate *pv;
+  guint length;
+
+  g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
+
+  length = clutter_text_buffer_get_length (buffer);
+  pv = buffer->priv;
+
+  if (n_chars < 0)
+    n_chars = g_utf8_strlen (chars, -1);
+
+  /* Bring position into bounds */
+  if (position > length)
+    position = length;
+
+  /* Make sure not entering too much data */
+  if (pv->max_length > 0)
+    {
+      if (length >= pv->max_length)
+        n_chars = 0;
+      else if (length + n_chars > pv->max_length)
+        n_chars -= (length + n_chars) - pv->max_length;
+    }
+
+  klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
+  g_return_val_if_fail (klass->insert_text != NULL, 0);
+
+  return (klass->insert_text) (buffer, position, chars, n_chars);
+}
+
+/**
+ * clutter_text_buffer_delete_text:
+ * @buffer: a #ClutterTextBuffer
+ * @position: position at which to delete text
+ * @n_chars: number of characters to delete
+ *
+ * Deletes a sequence of characters from the buffer. @n_chars characters are
+ * deleted starting at @position. If @n_chars is negative, then all characters
+ * until the end of the text are deleted.
+ *
+ * If @position or @n_chars are out of bounds, then they are coerced to sane
+ * values.
+ *
+ * Note that the positions are specified in characters, not bytes.
+ *
+ * Returns: The number of characters deleted.
+ *
+ * Since: 1.8
+ */
+guint
+clutter_text_buffer_delete_text (ClutterTextBuffer *buffer,
+                                 guint              position,
+                                 gint               n_chars)
+{
+  ClutterTextBufferClass *klass;
+  guint length;
+
+  g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
+
+  length = clutter_text_buffer_get_length (buffer);
+  if (n_chars < 0)
+    n_chars = length;
+  if (position > length)
+    position = length;
+  if (position + n_chars > length)
+    n_chars = length - position;
+
+  klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
+  g_return_val_if_fail (klass->delete_text != NULL, 0);
+
+  return (klass->delete_text) (buffer, position, n_chars);
+}
+
+/**
+ * clutter_text_buffer_emit_inserted_text:
+ * @buffer: a #ClutterTextBuffer
+ * @position: position at which text was inserted
+ * @chars: text that was inserted
+ * @n_chars: number of characters inserted
+ *
+ * Used when subclassing #ClutterTextBuffer
+ *
+ * Since: 1.8
+ */
+void
+clutter_text_buffer_emit_inserted_text (ClutterTextBuffer *buffer,
+                                        guint              position,
+                                        const gchar       *chars,
+                                        guint              n_chars)
+{
+  g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
+  g_signal_emit (buffer, signals[INSERTED_TEXT], 0, position, chars, n_chars);
+}
+
+/**
+ * clutter_text_buffer_emit_deleted_text:
+ * @buffer: a #ClutterTextBuffer
+ * @position: position at which text was deleted
+ * @n_chars: number of characters deleted
+ *
+ * Used when subclassing #ClutterTextBuffer
+ *
+ * Since: 1.8
+ */
+void
+clutter_text_buffer_emit_deleted_text (ClutterTextBuffer *buffer,
+                                       guint              position,
+                                       guint              n_chars)
+{
+  g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
+  g_signal_emit (buffer, signals[DELETED_TEXT], 0, position, n_chars);
+}
diff --git a/clutter/clutter-text-buffer.h b/clutter/clutter-text-buffer.h
new file mode 100644
index 0000000..0079ff3
--- /dev/null
+++ b/clutter/clutter-text-buffer.h
@@ -0,0 +1,139 @@
+/* clutter-text-buffer.h
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_TEXT_BUFFER_H__
+#define __CLUTTER_TEXT_BUFFER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* Maximum size of text buffer, in bytes */
+#define CLUTTER_TEXT_BUFFER_MAX_SIZE        G_MAXUSHORT
+
+#define CLUTTER_TYPE_TEXT_BUFFER            (clutter_text_buffer_get_type ())
+#define CLUTTER_TEXT_BUFFER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_TEXT_BUFFER, ClutterTextBuffer))
+#define CLUTTER_TEXT_BUFFER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_TEXT_BUFFER, ClutterTextBufferClass))
+#define CLUTTER_IS_TEXT_BUFFER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_TEXT_BUFFER))
+#define CLUTTER_IS_TEXT_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_TEXT_BUFFER))
+#define CLUTTER_TEXT_BUFFER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_TEXT_BUFFER, ClutterTextBufferClass))
+
+typedef struct _ClutterTextBuffer            ClutterTextBuffer;
+typedef struct _ClutterTextBufferClass       ClutterTextBufferClass;
+typedef struct _ClutterTextBufferPrivate     ClutterTextBufferPrivate;
+
+struct _ClutterTextBuffer
+{
+  GObject parent_instance;
+
+  /*< private >*/
+  ClutterTextBufferPrivate *priv;
+};
+
+struct _ClutterTextBufferClass
+{
+  GObjectClass parent_class;
+
+  /* Signals */
+
+  void         (*inserted_text)          (ClutterTextBuffer *buffer,
+                                          guint              position,
+                                          const gchar       *chars,
+                                          guint              n_chars);
+
+  void         (*deleted_text)           (ClutterTextBuffer *buffer,
+                                          guint              position,
+                                          guint              n_chars);
+
+  /* Virtual Methods */
+
+  const gchar* (*get_text)               (ClutterTextBuffer *buffer,
+                                          gsize             *n_bytes);
+
+  guint        (*get_length)             (ClutterTextBuffer *buffer);
+
+  guint        (*insert_text)            (ClutterTextBuffer *buffer,
+                                          guint              position,
+                                          const gchar       *chars,
+                                          guint              n_chars);
+
+  guint        (*delete_text)            (ClutterTextBuffer *buffer,
+                                          guint              position,
+                                          guint              n_chars);
+
+  /* Padding for future expansion */
+  void (*_clutter_reserved1) (void);
+  void (*_clutter_reserved2) (void);
+  void (*_clutter_reserved3) (void);
+  void (*_clutter_reserved4) (void);
+  void (*_clutter_reserved5) (void);
+  void (*_clutter_reserved6) (void);
+  void (*_clutter_reserved7) (void);
+  void (*_clutter_reserved8) (void);
+};
+
+GType               clutter_text_buffer_get_type            (void) G_GNUC_CONST;
+
+ClutterTextBuffer*  clutter_text_buffer_new                 (void);
+
+ClutterTextBuffer*  clutter_text_buffer_new_with_text       (const gchar       *text,
+                                                             gssize             text_len);
+
+gsize               clutter_text_buffer_get_bytes           (ClutterTextBuffer *buffer);
+
+guint               clutter_text_buffer_get_length          (ClutterTextBuffer *buffer);
+
+const gchar*        clutter_text_buffer_get_text            (ClutterTextBuffer *buffer);
+
+void                clutter_text_buffer_set_text            (ClutterTextBuffer *buffer,
+                                                             const gchar       *chars,
+                                                             gint               n_chars);
+
+void                clutter_text_buffer_set_max_length      (ClutterTextBuffer *buffer,
+                                                             gint               max_length);
+
+gint                clutter_text_buffer_get_max_length      (ClutterTextBuffer  *buffer);
+
+guint               clutter_text_buffer_insert_text         (ClutterTextBuffer *buffer,
+                                                             guint              position,
+                                                             const gchar       *chars,
+                                                             gint               n_chars);
+
+guint               clutter_text_buffer_delete_text         (ClutterTextBuffer *buffer,
+                                                             guint              position,
+                                                             gint               n_chars);
+
+void                clutter_text_buffer_emit_inserted_text  (ClutterTextBuffer *buffer,
+                                                             guint              position,
+                                                             const gchar       *chars,
+                                                             guint              n_chars);
+
+void                clutter_text_buffer_emit_deleted_text   (ClutterTextBuffer *buffer,
+                                                             guint              position,
+                                                             guint              n_chars);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_TEXT_BUFFER_H__ */
diff --git a/clutter/clutter-text.c b/clutter/clutter-text.c
index 5524f16..63b2451 100644
--- a/clutter/clutter-text.c
+++ b/clutter/clutter-text.c
@@ -38,8 +38,6 @@
  * #ClutterText is available since Clutter 1.0
  */
 
-/* TODO: undo/redo hooks? */
-
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -58,6 +56,7 @@
 #include "clutter-marshal.h"
 #include "clutter-private.h"    /* includes <cogl-pango/cogl-pango.h> */
 #include "clutter-profile.h"
+#include "clutter-text-buffer.h"
 #include "clutter-units.h"
 #include "clutter-paint-volume-private.h"
 #include "clutter-scriptable.h"
@@ -112,11 +111,8 @@ struct _ClutterTextPrivate
 {
   PangoFontDescription *font_desc;
 
-  /* the text passed to set_text and set_markup */
-  gchar *contents;
-
   /* the displayed text */
-  gchar *text;
+  ClutterTextBuffer *buffer;
 
   gchar *font_name;
 
@@ -162,12 +158,6 @@ struct _ClutterTextPrivate
    * default for now */
   gint text_y;
 
-  /* the length of the text, in bytes */
-  gint n_bytes;
-
-  /* the length of the text, in characters */
-  gint n_chars;
-
   /* Where to draw the cursor */
   ClutterGeometry cursor_pos;
   ClutterColor cursor_color;
@@ -184,8 +174,6 @@ struct _ClutterTextPrivate
 
   ClutterColor selected_text_color;
 
-  gint max_length;
-
   gunichar password_char;
 
   guint password_hint_id;
@@ -226,6 +214,7 @@ enum
 {
   PROP_0,
 
+  PROP_BUFFER,
   PROP_FONT_NAME,
   PROP_FONT_DESCRIPTION,
   PROP_TEXT,
@@ -273,6 +262,9 @@ enum
 static guint text_signals[LAST_SIGNAL] = { 0, };
 
 static void clutter_text_settings_changed_cb (ClutterText *text);
+static void buffer_connect_signals (ClutterText *self);
+static void buffer_disconnect_signals (ClutterText *self);
+static ClutterTextBuffer *get_buffer (ClutterText *self);
 
 static void
 clutter_text_dirty_paint_volume (ClutterText *text)
@@ -342,22 +334,30 @@ static gchar *
 clutter_text_get_display_text (ClutterText *self)
 {
   ClutterTextPrivate *priv = self->priv;
+  ClutterTextBuffer *buffer;
+  const gchar *text;
+
+  buffer = get_buffer (self);
+  text = clutter_text_buffer_get_text (buffer);
 
   /* simple short-circuit to avoid going through GString
    * with an empty text and a password char set
    */
-  if (priv->text[0] == '\0')
+  if (text[0] == '\0')
     return g_strdup ("");
 
-  if (priv->password_char == 0)
-    return g_strndup (priv->text, priv->n_bytes);
+  if (G_LIKELY (priv->password_char == 0))
+    return g_strdup (text);
   else
     {
-      GString *str = g_string_sized_new (priv->n_bytes);
+      GString *str;
       gunichar invisible_char;
       gchar buf[7];
       gint char_len, i;
+      guint n_chars;
 
+      n_chars = clutter_text_buffer_get_length (buffer);
+      str = g_string_sized_new (clutter_text_buffer_get_bytes (buffer));
       invisible_char = priv->password_char;
 
       /* we need to convert the string built of invisible
@@ -371,15 +371,15 @@ clutter_text_get_display_text (ClutterText *self)
         {
           char *last_char;
 
-          for (i = 0; i < priv->n_chars - 1; i++)
+          for (i = 0; i < n_chars - 1; i++)
             g_string_append_len (str, buf, char_len);
 
-          last_char = g_utf8_offset_to_pointer (priv->text, priv->n_chars - 1);
+          last_char = g_utf8_offset_to_pointer (text, n_chars - 1);
           g_string_append (str, last_char);
         }
       else
         {
-          for (i = 0; i < priv->n_chars; i++)
+          for (i = 0; i < n_chars; i++)
             g_string_append_len (str, buf, char_len);
         }
 
@@ -570,7 +570,7 @@ clutter_text_set_font_description_internal (ClutterText          *self,
 
   clutter_text_dirty_cache (self);
 
-  if (priv->text[0] != '\0')
+  if (clutter_text_buffer_get_length (get_buffer (self)) != 0)
     clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
 
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_FONT_DESCRIPTION]);
@@ -868,15 +868,15 @@ clutter_text_position_to_coords (ClutterText *self,
   gint n_chars;
   gint password_char_bytes = 1;
   gint index_;
+  gsize n_bytes;
 
   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
 
   priv = self->priv;
 
+  n_chars = clutter_text_buffer_get_length (get_buffer (self));
   if (priv->preedit_set)
-    n_chars = priv->n_chars + priv->preedit_n_chars;
-  else
-    n_chars = priv->n_chars;
+    n_chars += priv->preedit_n_chars;
 
   if (position < -1 || position > n_chars)
     return FALSE;
@@ -888,10 +888,11 @@ clutter_text_position_to_coords (ClutterText *self,
     {
       if (priv->password_char == 0)
         {
+          n_bytes = clutter_text_buffer_get_bytes (get_buffer (self));
           if (priv->editable && priv->preedit_set)
-            index_ = priv->n_bytes + strlen (priv->preedit_str);
+            index_ = n_bytes + strlen (priv->preedit_str);
           else
-            index_ = priv->n_bytes;
+            index_ = n_bytes;
         }
       else
         index_ = n_chars * password_char_bytes;
@@ -957,7 +958,7 @@ clutter_text_ensure_cursor_position (ClutterText *self)
   if (priv->editable && priv->preedit_set)
     {
       if (position == -1)
-        position = priv->n_chars;
+        position = clutter_text_buffer_get_length (get_buffer (self));
       position += priv->preedit_cursor_pos;
     }
 
@@ -1009,16 +1010,18 @@ clutter_text_delete_selection (ClutterText *self)
   gint start_index;
   gint end_index;
   gint old_position, old_selection;
+  guint n_chars;
 
   g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
 
   priv = self->priv;
 
-  if (priv->text[0] == '\0')
+  n_chars = clutter_text_buffer_get_length (get_buffer (self));
+  if (n_chars == 0)
     return TRUE;
 
-  start_index = offset_real (priv->text, priv->position);
-  end_index = offset_real (priv->text, priv->selection_bound);
+  start_index = priv->position == -1 ? n_chars : priv->position;
+  end_index = priv->selection_bound == -1 ? n_chars : priv->selection_bound;
 
   if (end_index == start_index)
     return FALSE;
@@ -1064,95 +1067,6 @@ clutter_text_set_positions (ClutterText *self,
 }
 
 static inline void
-clutter_text_set_contents (ClutterText *self,
-                           const gchar *str)
-{
-  ClutterTextPrivate *priv = self->priv;
-
-  g_free (priv->contents);
-
-  if (str == NULL || *str == '\0')
-    priv->contents = g_strdup ("");
-  else
-    priv->contents = g_strdup (str);
-}
-
-static inline void
-clutter_text_set_text_internal (ClutterText *self,
-                                const gchar *text)
-{
-  ClutterTextPrivate *priv = self->priv;
-
-  g_assert (text != NULL);
-
-  g_signal_emit (self, text_signals[DELETE_TEXT], 0, 0, -1);
-
-  /* emit ::insert-text only if we have text to insert; we need
-   * to emit this before actually changing the contents of the
-   * actor so that people connected to this signal will be able
-   * to intercept it
-   */
-  if (text[0] != '\0')
-    {
-      gint tmp_pos = 0;
-
-      g_signal_emit (self, text_signals[INSERT_TEXT], 0,
-                     text,
-                     strlen (text),
-                     &tmp_pos);
-    }
-
-  g_object_freeze_notify (G_OBJECT (self));
-
-  if (priv->max_length > 0)
-    {
-      gint len = g_utf8_strlen (text, -1);
-
-      if (len < priv->max_length)
-        {
-           g_free (priv->text);
-
-           priv->text = g_strdup (text);
-           priv->n_bytes = strlen (text);
-           priv->n_chars = len;
-        }
-      else
-        {
-          gchar *p = g_utf8_offset_to_pointer (text, priv->max_length);
-          gchar *n = g_malloc0 ((p - text) + 1);
-
-          g_free (priv->text);
-
-          g_utf8_strncpy (n, text, priv->max_length);
-
-          priv->text = n;
-          priv->n_bytes = strlen (n);
-          priv->n_chars = priv->max_length;
-        }
-    }
-  else
-    {
-      g_free (priv->text);
-
-      priv->text = g_strdup (text);
-      priv->n_bytes = strlen (text);
-      priv->n_chars = g_utf8_strlen (text, -1);
-    }
-
-  if (priv->n_bytes == 0)
-    clutter_text_set_positions (self, -1, -1);
-
-  clutter_text_dirty_cache (self);
-
-  clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
-
-  g_signal_emit (self, text_signals[TEXT_CHANGED], 0);
-  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_TEXT]);
-
-  g_object_thaw_notify (G_OBJECT (self));
-}
-
-static inline void
 clutter_text_set_markup_internal (ClutterText *self,
                                   const gchar *str)
 {
@@ -1187,9 +1101,11 @@ clutter_text_set_markup_internal (ClutterText *self,
       return;
     }
 
-  clutter_text_set_text_internal (self, text ? text : "");
-
-  g_free (text);
+  if (text)
+    {
+      clutter_text_buffer_set_text (get_buffer (self), text, strlen (text));
+      g_free (text);
+    }
 
   /* Store the new markup attributes */
   if (priv->markup_attrs != NULL)
@@ -1216,16 +1132,17 @@ clutter_text_set_property (GObject      *gobject,
 
   switch (prop_id)
     {
+    case PROP_BUFFER:
+      clutter_text_set_buffer (self, g_value_get_object (value));
+      break;
+
     case PROP_TEXT:
       {
         const char *str = g_value_get_string (value);
-
-        clutter_text_set_contents (self, str);
-
         if (self->priv->use_markup)
           clutter_text_set_markup_internal (self, str ? str : "");
         else
-          clutter_text_set_text_internal (self, str ? str : "");
+          clutter_text_buffer_set_text (get_buffer (self), str ? str : "", -1);
       }
       break;
 
@@ -1332,12 +1249,17 @@ clutter_text_get_property (GObject    *gobject,
                            GValue     *value,
                            GParamSpec *pspec)
 {
-  ClutterTextPrivate *priv = CLUTTER_TEXT (gobject)->priv;
+  ClutterText *self = CLUTTER_TEXT (gobject);
+  ClutterTextPrivate *priv = self->priv;
 
   switch (prop_id)
     {
+    case PROP_BUFFER:
+      g_value_set_object (value, clutter_text_get_buffer (self));
+      break;
+
     case PROP_TEXT:
-      g_value_set_string (value, priv->text);
+      g_value_set_string (value, clutter_text_buffer_get_text (get_buffer (self)));
       break;
 
     case PROP_FONT_NAME:
@@ -1405,7 +1327,7 @@ clutter_text_get_property (GObject    *gobject,
       break;
 
     case PROP_MAX_LENGTH:
-      g_value_set_int (value, priv->max_length);
+      g_value_set_int (value, clutter_text_buffer_get_max_length (get_buffer (self)));
       break;
 
     case PROP_SINGLE_LINE_MODE:
@@ -1477,6 +1399,8 @@ clutter_text_dispose (GObject *gobject)
       priv->password_hint_id = 0;
     }
 
+  clutter_text_set_buffer (self, NULL);
+
   G_OBJECT_CLASS (clutter_text_parent_class)->dispose (gobject);
 }
 
@@ -1500,8 +1424,7 @@ clutter_text_finalize (GObject *gobject)
 
   clutter_text_dirty_paint_volume (self);
 
-  g_free (priv->contents);
-  g_free (priv->text);
+  clutter_text_set_buffer (self, NULL);
   g_free (priv->font_name);
 
   G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject);
@@ -1698,7 +1621,7 @@ clutter_text_move_word_backward (ClutterText *self,
 {
   gint retval = start;
 
-  if (start > 0)
+  if (clutter_text_buffer_get_length (get_buffer (self)) > 0 && start > 0)
     {
       PangoLayout *layout = clutter_text_get_layout (self);
       PangoLogAttr *log_attrs = NULL;
@@ -1720,10 +1643,11 @@ static gint
 clutter_text_move_word_forward (ClutterText *self,
                                 gint         start)
 {
-  ClutterTextPrivate *priv = self->priv;
   gint retval = start;
+  guint n_chars;
 
-  if (start < priv->n_chars)
+  n_chars = clutter_text_buffer_get_length (get_buffer (self));
+  if (n_chars > 0 && start < n_chars)
     {
       PangoLayout *layout = clutter_text_get_layout (self);
       PangoLogAttr *log_attrs = NULL;
@@ -1732,7 +1656,7 @@ clutter_text_move_word_forward (ClutterText *self,
       pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
 
       retval = start + 1;
-      while (retval < priv->n_chars && !log_attrs[retval].is_word_end)
+      while (retval < n_chars && !log_attrs[retval].is_word_end)
         retval += 1;
 
       g_free (log_attrs);
@@ -1745,19 +1669,20 @@ static gint
 clutter_text_move_line_start (ClutterText *self,
                               gint         start)
 {
-  ClutterTextPrivate *priv = self->priv;
   PangoLayoutLine *layout_line;
   PangoLayout *layout;
   gint line_no;
   gint index_;
   gint position;
+  const gchar *text;
 
   layout = clutter_text_get_layout (self);
+  text = clutter_text_buffer_get_text (get_buffer (self));
 
   if (start == 0)
     index_ = 0;
   else
-    index_ = offset_to_bytes (priv->text, start);
+    index_ = offset_to_bytes (text, start);
 
   pango_layout_index_to_line_x (layout, index_,
                                 0,
@@ -1769,7 +1694,7 @@ clutter_text_move_line_start (ClutterText *self,
 
   pango_layout_line_x_to_index (layout_line, 0, &index_, NULL);
 
-  position = bytes_to_offset (priv->text, index_);
+  position = bytes_to_offset (text, index_);
 
   return position;
 }
@@ -1785,13 +1710,15 @@ clutter_text_move_line_end (ClutterText *self,
   gint index_;
   gint trailing;
   gint position;
+  const gchar *text;
 
   layout = clutter_text_get_layout (self);
+  text = clutter_text_buffer_get_text (get_buffer (self));
 
   if (start == 0)
     index_ = 0;
   else
-    index_ = offset_to_bytes (priv->text, priv->position);
+    index_ = offset_to_bytes (text, priv->position);
 
   pango_layout_index_to_line_x (layout, index_,
                                 0,
@@ -1804,7 +1731,7 @@ clutter_text_move_line_end (ClutterText *self,
   pango_layout_line_x_to_index (layout_line, G_MAXINT, &index_, &trailing);
   index_ += trailing;
 
-  position = bytes_to_offset (priv->text, index_);
+  position = bytes_to_offset (text, index_);
 
   return position;
 }
@@ -1860,7 +1787,7 @@ clutter_text_button_press (ClutterActor       *actor,
    * set up the dragging of the selection since there's nothing
    * to select
    */
-  if (priv->text[0] == '\0')
+  if (clutter_text_buffer_get_length (get_buffer (self)) == 0)
     {
       clutter_text_set_positions (self, -1, -1);
 
@@ -1874,9 +1801,11 @@ clutter_text_button_press (ClutterActor       *actor,
   if (res)
     {
       gint offset;
+      const char *text;
 
       index_ = clutter_text_coords_to_position (self, x, y);
-      offset = bytes_to_offset (priv->text, index_);
+      text = clutter_text_buffer_get_text (get_buffer (self));
+      offset = bytes_to_offset (text, index_);
 
       /* what we select depends on the number of button clicks we
        * receive:
@@ -1915,6 +1844,7 @@ clutter_text_motion (ClutterActor       *actor,
   gfloat x, y;
   gint index_, offset;
   gboolean res;
+  const gchar *text;
 
   if (!priv->in_select_drag)
     return CLUTTER_EVENT_PROPAGATE;
@@ -1926,7 +1856,8 @@ clutter_text_motion (ClutterActor       *actor,
     return CLUTTER_EVENT_PROPAGATE;
 
   index_ = clutter_text_coords_to_position (self, x, y);
-  offset = bytes_to_offset (priv->text, index_);
+  text = clutter_text_buffer_get_text (get_buffer (self));
+  offset = bytes_to_offset (text, index_);
 
   if (priv->selectable)
     clutter_text_set_cursor_position (self, offset);
@@ -2057,21 +1988,22 @@ clutter_text_paint (ClutterActor *self)
   guint8 real_opacity;
   gint text_x = priv->text_x;
   gboolean clip_set = FALSE;
+  guint n_chars;
 
   /* Note that if anything in this paint method changes it needs to be
      reflected in the get_paint_volume implementation which is tightly
      tied to the workings of this function */
 
-  if (G_UNLIKELY (priv->font_desc == NULL))
+  n_chars = clutter_text_buffer_get_length (get_buffer (text));
+  if (G_UNLIKELY (priv->font_desc == NULL || n_chars == 0))
     {
-      CLUTTER_NOTE (ACTOR, "No font description for '%s'",
-                    _clutter_actor_get_debug_name (self));
+      CLUTTER_NOTE (ACTOR, "desc: %p",
+                    priv->font_desc ? priv->font_desc : 0x0);
       return;
     }
 
   /* don't bother painting an empty text actor */
-  if (priv->text[0] == '\0' &&
-      (!priv->editable || !priv->cursor_visible))
+  if (n_chars > 0 && (!priv->editable || !priv->cursor_visible))
     return;
 
   clutter_actor_get_allocation_box (self, &alloc);
@@ -2190,7 +2122,8 @@ clutter_text_paint (ClutterActor *self)
                * priv->text_color.alpha
                / 255;
 
-  CLUTTER_NOTE (PAINT, "painting text (text: '%s')", priv->text);
+  CLUTTER_NOTE (PAINT, "painting text (text: '%s')",
+                clutter_text_buffer_get_text (get_buffer (text)));
 
   cogl_color_init_from_4ub (&color,
                             priv->text_color.red,
@@ -2500,7 +2433,7 @@ clutter_text_real_move_left (ClutterText         *self,
   gint new_pos = 0;
   gint len;
 
-  len = priv->n_chars;
+  len = clutter_text_buffer_get_length (get_buffer (self));
 
   g_object_freeze_notify (G_OBJECT (self));
 
@@ -2540,7 +2473,7 @@ clutter_text_real_move_right (ClutterText         *self,
 {
   ClutterTextPrivate *priv = self->priv;
   gint pos = priv->position;
-  gint len = priv->n_chars;
+  gint len = clutter_text_buffer_get_length (get_buffer (self));
   gint new_pos = 0;
 
   g_object_freeze_notify (G_OBJECT (self));
@@ -2582,13 +2515,15 @@ clutter_text_real_move_up (ClutterText         *self,
   gint index_, trailing;
   gint pos;
   gint x;
+  const gchar *text;
 
   layout = clutter_text_get_layout (self);
+  text = clutter_text_buffer_get_text (get_buffer (self));
 
   if (priv->position == 0)
     index_ = 0;
   else
-    index_ = offset_to_bytes (priv->text, priv->position);
+    index_ = offset_to_bytes (text, priv->position);
 
   pango_layout_index_to_line_x (layout, index_,
                                 0,
@@ -2609,7 +2544,7 @@ clutter_text_real_move_up (ClutterText         *self,
 
   g_object_freeze_notify (G_OBJECT (self));
 
-  pos = bytes_to_offset (priv->text, index_);
+  pos = bytes_to_offset (text, index_);
   clutter_text_set_cursor_position (self, pos + trailing);
 
   /* Store the target x position to avoid drifting left and right when
@@ -2637,13 +2572,15 @@ clutter_text_real_move_down (ClutterText         *self,
   gint index_, trailing;
   gint x;
   gint pos;
+  const gchar *text;
 
   layout = clutter_text_get_layout (self);
+  text = clutter_text_buffer_get_text (get_buffer (self));
 
   if (priv->position == 0)
     index_ = 0;
   else
-    index_ = offset_to_bytes (priv->text, priv->position);
+    index_ = offset_to_bytes (text, priv->position);
 
   pango_layout_index_to_line_x (layout, index_,
                                 0,
@@ -2660,7 +2597,7 @@ clutter_text_real_move_down (ClutterText         *self,
 
   g_object_freeze_notify (G_OBJECT (self));
 
-  pos = bytes_to_offset (priv->text, index_);
+  pos = bytes_to_offset (text, index_);
   clutter_text_set_cursor_position (self, pos + trailing);
 
   /* Store the target x position to avoid drifting left and right when
@@ -2725,7 +2662,8 @@ clutter_text_real_select_all (ClutterText         *self,
                               guint                keyval,
                               ClutterModifierType  modifiers)
 {
-  clutter_text_set_positions (self, 0, self->priv->n_chars);
+  guint n_chars = clutter_text_buffer_get_length (get_buffer (self));
+  clutter_text_set_positions (self, 0, n_chars);
 
   return TRUE;
 }
@@ -2744,7 +2682,7 @@ clutter_text_real_del_next (ClutterText         *self,
     return TRUE;
 
   pos = priv->position;
-  len = priv->n_chars;
+  len = clutter_text_buffer_get_length (get_buffer (self));
 
   if (len && pos != -1 && pos < len)
     clutter_text_delete_text (self, pos, pos + 1);
@@ -2763,7 +2701,7 @@ clutter_text_real_del_word_next (ClutterText         *self,
   gint len;
 
   pos = priv->position;
-  len = priv->n_chars;
+  len = clutter_text_buffer_get_length (get_buffer (self));
 
   if (len && pos != -1 && pos < len)
     {
@@ -2802,7 +2740,7 @@ clutter_text_real_del_prev (ClutterText         *self,
     return TRUE;
 
   pos = priv->position;
-  len = priv->n_chars;
+  len = clutter_text_buffer_get_length (get_buffer (self));
 
   if (pos != 0 && len != 0)
     {
@@ -2834,7 +2772,7 @@ clutter_text_real_del_word_prev (ClutterText         *self,
   gint len;
 
   pos = priv->position;
-  len = priv->n_chars;
+  len = clutter_text_buffer_get_length (get_buffer (self));
 
   if (pos != 0 && len != 0)
     {
@@ -2984,6 +2922,23 @@ clutter_text_class_init (ClutterTextClass *klass)
   actor_class->has_overlaps = clutter_text_has_overlaps;
 
   /**
+   * ClutterText:buffer:
+   *
+   * The buffer which stores the text for this #ClutterText.
+   *
+   * If set to %NULL, a default buffer will be created.
+   *
+   * Since: 1.8
+   */
+  pspec = g_param_spec_object ("buffer",
+                               P_("Buffer"),
+                               P_("The buffer for the text"),
+                               CLUTTER_TYPE_TEXT_BUFFER,
+                               CLUTTER_PARAM_READWRITE);
+  obj_props[PROP_BUFFER] = pspec;
+  g_object_class_install_property (gobject_class, PROP_BUFFER, pspec);
+
+  /**
    * ClutterText:font-name:
    *
    * The font to be used by the #ClutterText, as a string
@@ -3653,8 +3608,7 @@ clutter_text_init (ClutterText *self)
    * return a valid string and we can safely call strlen()
    * or strcmp() on it
    */
-  priv->text = g_strdup ("");
-  priv->contents = g_strdup ("");
+  priv->buffer = NULL;
 
   priv->text_color = default_text_color;
   priv->cursor_color = default_cursor_color;
@@ -3692,8 +3646,6 @@ clutter_text_init (ClutterText *self)
   priv->show_password_hint = password_hint_time > 0;
   priv->password_hint_timeout = password_hint_time;
 
-  priv->max_length = 0;
-
   priv->text_y = 0;
 
   priv->cursor_size = DEFAULT_CURSOR_SIZE;
@@ -3782,6 +3734,213 @@ clutter_text_new_with_text (const gchar *font_name,
                        NULL);
 }
 
+static ClutterTextBuffer*
+get_buffer (ClutterText *self)
+{
+  ClutterTextPrivate *priv = self->priv;
+
+  if (priv->buffer == NULL)
+    {
+      ClutterTextBuffer *buffer;
+      buffer = clutter_text_buffer_new ();
+      clutter_text_set_buffer (self, buffer);
+      g_object_unref (buffer);
+    }
+
+  return priv->buffer;
+}
+
+/* GtkEntryBuffer signal handlers
+ */
+static void
+buffer_inserted_text (ClutterTextBuffer *buffer,
+                      guint              position,
+                      const gchar       *chars,
+                      guint              n_chars,
+                      ClutterText       *self)
+{
+  ClutterTextPrivate *priv;
+  gint new_position;
+  gint new_selection_bound;
+  gsize n_bytes;
+
+  priv = self->priv;
+  if (priv->position >= 0 || priv->selection_bound >= 0)
+    {
+      new_position = priv->position;
+      new_selection_bound = priv->selection_bound;
+
+      if (position <= new_position)
+        new_position += n_chars;
+      if (position <= new_selection_bound)
+        new_selection_bound += n_chars;
+
+      if (priv->position != new_position || priv->selection_bound != new_selection_bound)
+        clutter_text_set_positions (self, new_position, new_selection_bound);
+    }
+
+  n_bytes = g_utf8_offset_to_pointer (chars, n_chars) - chars;
+  g_signal_emit (self, text_signals[INSERT_TEXT], 0, chars,
+                 n_bytes, &position);
+
+  /* TODO: What are we supposed to with the out value of position? */
+}
+
+static void
+buffer_deleted_text (ClutterTextBuffer *buffer,
+                     guint              position,
+                     guint              n_chars,
+                     ClutterText       *self)
+{
+  ClutterTextPrivate *priv;
+  gint new_position;
+  gint new_selection_bound;
+
+  priv = self->priv;
+  if (priv->position >= 0 || priv->selection_bound >= 0)
+    {
+      new_position = priv->position;
+      new_selection_bound = priv->selection_bound;
+
+      if (position < new_position)
+        new_position -= n_chars;
+      if (position < new_selection_bound)
+        new_selection_bound -= n_chars;
+
+      if (priv->position != new_position || priv->selection_bound != new_selection_bound)
+        clutter_text_set_positions (self, new_position, new_selection_bound);
+    }
+
+  g_signal_emit (self, text_signals[DELETE_TEXT], 0, position, position + n_chars);
+}
+
+static void
+buffer_notify_text (ClutterTextBuffer *buffer,
+                    GParamSpec        *spec,
+                    ClutterText       *self)
+{
+  g_object_freeze_notify (G_OBJECT (self));
+
+  clutter_text_dirty_cache (self);
+
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
+
+  g_signal_emit (self, text_signals[TEXT_CHANGED], 0);
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_TEXT]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+buffer_notify_max_length (ClutterTextBuffer *buffer,
+                          GParamSpec        *spec,
+                          ClutterText       *self)
+{
+  g_object_notify (G_OBJECT (self), "max-length");
+}
+
+static void
+buffer_connect_signals (ClutterText *self)
+{
+  ClutterTextPrivate *priv = self->priv;
+  g_signal_connect (priv->buffer, "inserted-text", G_CALLBACK (buffer_inserted_text), self);
+  g_signal_connect (priv->buffer, "deleted-text", G_CALLBACK (buffer_deleted_text), self);
+  g_signal_connect (priv->buffer, "notify::text", G_CALLBACK (buffer_notify_text), self);
+  g_signal_connect (priv->buffer, "notify::max-length", G_CALLBACK (buffer_notify_max_length), self);
+}
+
+static void
+buffer_disconnect_signals (ClutterText *self)
+{
+  ClutterTextPrivate *priv = self->priv;
+  g_signal_handlers_disconnect_by_func (priv->buffer, buffer_inserted_text, self);
+  g_signal_handlers_disconnect_by_func (priv->buffer, buffer_deleted_text, self);
+  g_signal_handlers_disconnect_by_func (priv->buffer, buffer_notify_text, self);
+  g_signal_handlers_disconnect_by_func (priv->buffer, buffer_notify_max_length, self);
+}
+
+/**
+ * clutter_text_new_with_buffer:
+ * @buffer: The buffer to use for the new #ClutterText.
+ *
+ * Creates a new entry with the specified text buffer.
+ *
+ * Return value: a new #ClutterText
+ *
+ * Since: 1.8
+ */
+ClutterActor *
+clutter_text_new_with_buffer (ClutterTextBuffer *buffer)
+{
+  g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), NULL);
+  return g_object_new (CLUTTER_TYPE_TEXT, "buffer", buffer, NULL);
+}
+
+/**
+ * clutter_text_get_buffer:
+ * @self: a #ClutterText
+ *
+ * Get the #ClutterTextBuffer object which holds the text for
+ * this widget.
+ *
+ * Returns: (transfer none): A #GtkEntryBuffer object.
+ *
+ * Since: 1.8
+ */
+ClutterTextBuffer*
+clutter_text_get_buffer (ClutterText *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
+
+  return get_buffer (self);
+}
+
+/**
+ * clutter_text_set_buffer:
+ * @self: a #ClutterText
+ * @buffer: a #ClutterTextBuffer
+ *
+ * Set the #ClutterTextBuffer object which holds the text for
+ * this widget.
+ *
+ * Since: 1.8
+ */
+void
+clutter_text_set_buffer (ClutterText       *self,
+                         ClutterTextBuffer *buffer)
+{
+  ClutterTextPrivate *priv;
+  GObject *obj;
+
+  g_return_if_fail (CLUTTER_IS_TEXT (self));
+
+  priv = self->priv;
+
+  if (buffer)
+    {
+      g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
+      g_object_ref (buffer);
+    }
+
+  if (priv->buffer)
+    {
+      buffer_disconnect_signals (self);
+      g_object_unref (priv->buffer);
+    }
+
+  priv->buffer = buffer;
+
+  if (priv->buffer)
+     buffer_connect_signals (self);
+
+  obj = G_OBJECT (self);
+  g_object_freeze_notify (obj);
+  g_object_notify (obj, "buffer");
+  g_object_notify (obj, "text");
+  g_object_notify (obj, "max-length");
+  g_object_thaw_notify (obj);
+}
+
 /**
  * clutter_text_set_editable:
  * @self: a #ClutterText
@@ -4105,17 +4264,16 @@ clutter_text_set_selection (ClutterText *self,
                             gssize       start_pos,
                             gssize       end_pos)
 {
-  ClutterTextPrivate *priv;
+  guint n_chars;
 
   g_return_if_fail (CLUTTER_IS_TEXT (self));
 
-  priv = self->priv;
-
+  n_chars = clutter_text_buffer_get_length (get_buffer (self));
   if (end_pos < 0)
-    end_pos = priv->n_chars;
+    end_pos = n_chars;
 
-  start_pos = MIN (priv->n_chars, start_pos);
-  end_pos = MIN (priv->n_chars, end_pos);
+  start_pos = MIN (n_chars, start_pos);
+  end_pos = MIN (n_chars, end_pos);
 
   clutter_text_set_positions (self, start_pos, end_pos);
 }
@@ -4140,6 +4298,7 @@ clutter_text_get_selection (ClutterText *self)
   gint len;
   gint start_index, end_index;
   gint start_offset, end_offset;
+  const gchar *text;
 
   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
 
@@ -4159,12 +4318,13 @@ clutter_text_get_selection (ClutterText *self)
       end_index = temp;
     }
 
-  start_offset = offset_to_bytes (priv->text, start_index);
-  end_offset = offset_to_bytes (priv->text, end_index);
+  text = clutter_text_buffer_get_text (get_buffer (self));
+  start_offset = offset_to_bytes (text, start_index);
+  end_offset = offset_to_bytes (text, end_index);
   len = end_offset - start_offset;
 
   str = g_malloc (len + 1);
-  g_utf8_strncpy (str, priv->text + start_offset, end_index - start_index);
+  g_utf8_strncpy (str, text + start_offset, end_index - start_index);
 
   return str;
 }
@@ -4193,7 +4353,7 @@ clutter_text_set_selection_bound (ClutterText *self,
 
   if (priv->selection_bound != selection_bound)
     {
-      gint len = priv->n_chars;
+      gint len = clutter_text_buffer_get_length (get_buffer (self));;
 
       if (selection_bound < 0 || selection_bound >= len)
         priv->selection_bound = -1;
@@ -4513,7 +4673,7 @@ clutter_text_get_text (ClutterText *self)
 {
   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
 
-  return self->priv->text;
+  return clutter_text_buffer_get_text (get_buffer (self));
 }
 
 static inline void
@@ -4567,8 +4727,7 @@ clutter_text_set_text (ClutterText *self,
   g_return_if_fail (CLUTTER_IS_TEXT (self));
 
   clutter_text_set_use_markup_internal (self, FALSE);
-  clutter_text_set_contents (self, text);
-  clutter_text_set_text_internal (self, text ? text : "");
+  clutter_text_buffer_set_text (get_buffer (self), text, -1);
 }
 
 /**
@@ -4597,8 +4756,10 @@ clutter_text_set_markup (ClutterText *self,
   g_return_if_fail (CLUTTER_IS_TEXT (self));
 
   clutter_text_set_use_markup_internal (self, TRUE);
-  clutter_text_set_contents (self, markup);
-  clutter_text_set_markup_internal (self, markup ? markup : "");
+  if (markup != NULL && *markup != '\0')
+    clutter_text_set_markup_internal (self, markup);
+  else
+    clutter_text_buffer_set_text (get_buffer (self), "", 0);
 }
 
 /**
@@ -4980,23 +5141,16 @@ void
 clutter_text_set_use_markup (ClutterText *self,
 			     gboolean     setting)
 {
-  ClutterTextPrivate *priv;
-  gchar *str;
+  const gchar *text;
 
   g_return_if_fail (CLUTTER_IS_TEXT (self));
 
-  priv = self->priv;
-
-  str = g_strdup (priv->contents);
+  text = clutter_text_buffer_get_text (get_buffer (self));
 
   clutter_text_set_use_markup_internal (self, setting);
 
   if (setting)
-    clutter_text_set_markup_internal (self, str);
-  else
-    clutter_text_set_text_internal (self, str);
-
-  g_free (str);
+    clutter_text_set_markup_internal (self, text);
 
   clutter_text_dirty_cache (self);
 
@@ -5117,7 +5271,7 @@ clutter_text_set_cursor_position (ClutterText *self,
   if (priv->position == position)
     return;
 
-  len = priv->n_chars;
+  len = clutter_text_buffer_get_length (get_buffer (self));
 
   if (position < 0 || position >= len)
     priv->position = -1;
@@ -5256,26 +5410,8 @@ void
 clutter_text_set_max_length (ClutterText *self,
                              gint         max)
 {
-  ClutterTextPrivate *priv;
-  gchar *new = NULL;
-
   g_return_if_fail (CLUTTER_IS_TEXT (self));
-
-  priv = self->priv;
-
-  if (priv->max_length != max)
-    {
-      if (max < 0)
-        max = priv->n_chars;
-
-      priv->max_length = max;
-
-      new = g_strdup (priv->text);
-      clutter_text_set_text (self, new);
-      g_free (new);
-
-      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_MAX_LENGTH]);
-    }
+  clutter_text_buffer_set_max_length (get_buffer (self), max);
 }
 
 /**
@@ -5295,7 +5431,7 @@ clutter_text_get_max_length (ClutterText *self)
 {
   g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0);
 
-  return self->priv->max_length;
+  return clutter_text_buffer_get_max_length (get_buffer (self));
 }
 
 /**
@@ -5313,30 +5449,14 @@ clutter_text_insert_unichar (ClutterText *self,
                              gunichar     wc)
 {
   ClutterTextPrivate *priv;
-  GString *new = NULL;
-  glong pos;
-
-  g_return_if_fail (CLUTTER_IS_TEXT (self));
-  g_return_if_fail (g_unichar_validate (wc));
-
-  if (wc == 0)
-    return;
+  GString *new;
 
   priv = self->priv;
 
-  new = g_string_new (priv->text);
+  new = g_string_new ("");
+  g_string_append_unichar (new, wc);
 
-  pos = offset_to_bytes (priv->text, priv->position);
-  new = g_string_insert_unichar (new, pos, wc);
-
-  g_signal_emit (self, text_signals[INSERT_TEXT], 0, &wc, 1, &pos);
-
-  clutter_text_set_text_internal (self, new->str);
-
-  if (priv->position >= 0)
-    clutter_text_set_positions (self,
-                                priv->position + 1,
-                                priv->position + 1);
+  clutter_text_buffer_insert_text (get_buffer (self), priv->position, new->str, 1);
 
   g_string_free (new, TRUE);
 }
@@ -5361,35 +5481,11 @@ clutter_text_insert_text (ClutterText *self,
                           const gchar *text,
                           gssize       position)
 {
-  ClutterTextPrivate *priv;
-  GString *new = NULL;
-  gint pos_bytes;
-
   g_return_if_fail (CLUTTER_IS_TEXT (self));
   g_return_if_fail (text != NULL);
 
-  priv = self->priv;
-
-  pos_bytes = offset_to_bytes (priv->text, position);
-
-  new = g_string_new (priv->text);
-  new = g_string_insert (new, pos_bytes, text);
-
-  g_signal_emit (self, text_signals[INSERT_TEXT], 0,
-                 text,
-                 g_utf8_strlen (text, -1),
-                 &position);
-
-  clutter_text_set_text_internal (self, new->str);
-
-  if (position >= 0 && priv->position >= position)
-    {
-      gint new_pos = priv->position + g_utf8_strlen (text, -1);
-
-      clutter_text_set_positions (self, new_pos, new_pos);
-    }
-
-  g_string_free (new, TRUE);
+  clutter_text_buffer_insert_text (get_buffer (self), position, text,
+                                   g_utf8_strlen (text, -1));
 }
 
 /**
@@ -5411,36 +5507,9 @@ clutter_text_delete_text (ClutterText *self,
                           gssize       start_pos,
                           gssize       end_pos)
 {
-  ClutterTextPrivate *priv;
-  GString *new = NULL;
-  gint start_bytes;
-  gint end_bytes;
-
   g_return_if_fail (CLUTTER_IS_TEXT (self));
 
-  priv = self->priv;
-
-  if (priv->text[0] == '\0')
-    return;
-
-  if (start_pos == 0)
-    start_bytes = 0;
-  else
-    start_bytes = offset_to_bytes (priv->text, start_pos);
-
-  if (end_pos == -1)
-    end_bytes = offset_to_bytes (priv->text, priv->n_chars);
-  else
-    end_bytes = offset_to_bytes (priv->text, end_pos);
-
-  new = g_string_new (priv->text);
-  new = g_string_erase (new, start_bytes, end_bytes - start_bytes);
-
-  g_signal_emit (self, text_signals[DELETE_TEXT], 0, start_pos, end_pos);
-
-  clutter_text_set_text_internal (self, new->str);
-
-  g_string_free (new, TRUE);
+  clutter_text_buffer_delete_text (get_buffer (self), start_pos, end_pos - start_pos);
 }
 
 /**
@@ -5451,6 +5520,9 @@ clutter_text_delete_text (ClutterText *self,
  * Deletes @n_chars inside a #ClutterText actor, starting from the
  * current cursor position.
  *
+ * Somewhat awkwardly, the cursor position is decremented by the same
+ * number of characters you've deleted.
+ *
  * Since: 1.0
  */
 void
@@ -5458,44 +5530,15 @@ clutter_text_delete_chars (ClutterText *self,
                            guint        n_chars)
 {
   ClutterTextPrivate *priv;
-  GString *new = NULL;
-  gint pos;
-  gint num_pos;
-  gint start_pos;
 
   g_return_if_fail (CLUTTER_IS_TEXT (self));
 
   priv = self->priv;
 
-  if (priv->text[0] == '\0')
-    return;
-
-  new = g_string_new (priv->text);
-
-  if (priv->position == -1)
-    {
-      num_pos = offset_to_bytes (priv->text, priv->n_chars - n_chars);
-      new = g_string_erase (new, num_pos, -1);
-    }
-  else
-    {
-      pos = offset_to_bytes (priv->text, priv->position - n_chars);
-      num_pos = offset_to_bytes (priv->text, priv->position);
-      new = g_string_erase (new, pos, num_pos - pos);
-    }
-
-  start_pos = clutter_text_get_cursor_position (self);
-  g_signal_emit (self, text_signals[DELETE_TEXT], 0,
-                 start_pos, start_pos + n_chars);
-
-  clutter_text_set_text_internal (self, new->str);
+  clutter_text_buffer_delete_text (get_buffer (self), priv->position, n_chars);
 
   if (priv->position > 0)
     clutter_text_set_cursor_position (self, priv->position - n_chars);
-
-  g_string_free (new, TRUE);
-
-  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_TEXT]);
 }
 
 /**
@@ -5520,25 +5563,25 @@ clutter_text_get_chars (ClutterText *self,
                         gssize       start_pos,
                         gssize       end_pos)
 {
-  ClutterTextPrivate *priv;
   gint start_index, end_index;
+  guint n_chars;
+  const gchar *text;
 
   g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
 
-  priv = self->priv;
+  n_chars = clutter_text_buffer_get_length (get_buffer (self));
+  text = clutter_text_buffer_get_text (get_buffer (self));
 
   if (end_pos < 0)
-    end_pos = priv->n_chars;
+    end_pos = n_chars;
 
-  start_pos = MIN (priv->n_chars, start_pos);
-  end_pos = MIN (priv->n_chars, end_pos);
+  start_pos = MIN (n_chars, start_pos);
+  end_pos = MIN (n_chars, end_pos);
 
-  start_index = g_utf8_offset_to_pointer (priv->text, start_pos)
-              - priv->text;
-  end_index   = g_utf8_offset_to_pointer (priv->text, end_pos)
-              - priv->text;
+  start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
+  end_index   = g_utf8_offset_to_pointer (text, end_pos) - text;
 
-  return g_strndup (priv->text + start_index, end_index - start_index);
+  return g_strndup (text + start_index, end_index - start_index);
 }
 
 /**
diff --git a/clutter/clutter-text.h b/clutter/clutter-text.h
index c127fe7..9129978 100644
--- a/clutter/clutter-text.h
+++ b/clutter/clutter-text.h
@@ -30,6 +30,7 @@
 #define __CLUTTER_TEXT_H__
 
 #include <clutter/clutter-actor.h>
+#include <clutter/clutter-text-buffer.h>
 #include <pango/pango.h>
 
 G_BEGIN_DECLS
@@ -102,7 +103,10 @@ ClutterActor *        clutter_text_new_full             (const gchar          *f
                                                          const ClutterColor   *color);
 ClutterActor *        clutter_text_new_with_text        (const gchar          *font_name,
                                                          const gchar          *text);
-
+ClutterActor *        clutter_text_new_with_buffer      (ClutterTextBuffer    *buffer);
+ClutterTextBuffer *   clutter_text_get_buffer           (ClutterText          *self);
+void                  clutter_text_set_buffer           (ClutterText          *self,
+                                                         ClutterTextBuffer    *buffer);
 const gchar *         clutter_text_get_text             (ClutterText          *self);
 void                  clutter_text_set_text             (ClutterText          *self,
                                                          const gchar          *text);
diff --git a/tests/interactive/test-text.c b/tests/interactive/test-text.c
index f63cc5c..bcfd050 100644
--- a/tests/interactive/test-text.c
+++ b/tests/interactive/test-text.c
@@ -15,9 +15,10 @@ test_text_main (gint    argc,
                 gchar **argv)
 {
   ClutterActor *stage;
-  ClutterActor *text;
+  ClutterActor *text, *text2;
   ClutterColor  text_color = { 0x33, 0xff, 0x33, 0xff };
   ClutterColor  cursor_color = { 0xff, 0x33, 0x33, 0xff };
+  ClutterTextBuffer *buffer;
 
   if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
     return 1;
@@ -27,7 +28,11 @@ test_text_main (gint    argc,
   clutter_stage_set_color (CLUTTER_STAGE (stage), CLUTTER_COLOR_Black);
   g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
 
-  text = clutter_text_new_full (FONT, "Â", &text_color);
+  buffer = clutter_text_buffer_new_with_text ("Â", -1);
+
+  text = clutter_text_new_with_buffer (buffer);
+  clutter_text_set_font_name (CLUTTER_TEXT (text), FONT);
+  clutter_text_set_color (CLUTTER_TEXT (text), &text_color);
 
   clutter_container_add (CLUTTER_CONTAINER (stage), text, NULL);
   clutter_actor_set_position (text, 40, 30);
@@ -42,6 +47,19 @@ test_text_main (gint    argc,
   clutter_text_set_cursor_color (CLUTTER_TEXT (text), &cursor_color);
   clutter_text_set_selected_text_color (CLUTTER_TEXT (text), CLUTTER_COLOR_Blue);
 
+  text2 = clutter_text_new_with_buffer (buffer);
+  clutter_text_set_color (CLUTTER_TEXT (text2), &text_color);
+  clutter_container_add (CLUTTER_CONTAINER (stage), text2, NULL);
+  clutter_actor_set_position (text2, 40, 300);
+  clutter_actor_set_width (text2, 1024);
+  clutter_text_set_line_wrap (CLUTTER_TEXT (text2), TRUE);
+
+  clutter_actor_set_reactive (text2, TRUE);
+  clutter_text_set_editable (CLUTTER_TEXT (text2), TRUE);
+  clutter_text_set_selectable (CLUTTER_TEXT (text2), TRUE);
+  clutter_text_set_cursor_color (CLUTTER_TEXT (text2), &cursor_color);
+  clutter_text_set_selected_text_color (CLUTTER_TEXT (text2), CLUTTER_COLOR_Green);
+
   if (argv[1])
     {
       GError *error = NULL;
@@ -66,6 +84,8 @@ test_text_main (gint    argc,
 
   clutter_main ();
 
+  g_object_unref (stage);
+
   return EXIT_SUCCESS;
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]