Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-text-buffer.c
1 /* clutter-text-buffer.c
2  * Copyright (C) 2011 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: Stef Walter <stefw@collabora.co.uk>
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "clutter-text-buffer.h"
27 #include "clutter-marshal.h"
28 #include "clutter-private.h"
29
30 #include <string.h>
31
32 /**
33  * SECTION:clutter-text-buffer
34  * @title: ClutterTextBuffer
35  * @short_description: Text buffer for ClutterText
36  *
37  * The #ClutterTextBuffer class contains the actual text displayed in a
38  * #ClutterText widget.
39  *
40  * A single #ClutterTextBuffer object can be shared by multiple #ClutterText
41  * widgets which will then share the same text content, but not the cursor
42  * position, visibility attributes, icon etc.
43  *
44  * #ClutterTextBuffer may be derived from. Such a derived class might allow
45  * text to be stored in an alternate location, such as non-pageable memory,
46  * useful in the case of important passwords. Or a derived class could
47  * integrate with an application's concept of undo/redo.
48  *
49  * Since: 1.10
50  */
51
52 /* Initial size of buffer, in bytes */
53 #define MIN_SIZE 16
54
55 enum {
56   PROP_0,
57   PROP_TEXT,
58   PROP_LENGTH,
59   PROP_MAX_LENGTH,
60   PROP_LAST
61 };
62
63 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
64
65 enum {
66   INSERTED_TEXT,
67   DELETED_TEXT,
68   LAST_SIGNAL
69 };
70
71 static guint signals[LAST_SIGNAL] = { 0 };
72
73 struct _ClutterTextBufferPrivate
74 {
75   gint  max_length;
76
77   /* Only valid if this class is not derived */
78   gchar *normal_text;
79   gsize  normal_text_size;
80   gsize  normal_text_bytes;
81   guint  normal_text_chars;
82 };
83
84 G_DEFINE_TYPE (ClutterTextBuffer, clutter_text_buffer, G_TYPE_OBJECT);
85
86 /* --------------------------------------------------------------------------------
87  * DEFAULT IMPLEMENTATIONS OF TEXT BUFFER
88  *
89  * These may be overridden by a derived class, behavior may be changed etc...
90  * The normal_text and normal_text_xxxx fields may not be valid when
91  * this class is derived from.
92  */
93
94 /* Overwrite a memory that might contain sensitive information. */
95 static void
96 trash_area (gchar *area,
97             gsize  len)
98 {
99   volatile gchar *varea = (volatile gchar *)area;
100   while (len-- > 0)
101     *varea++ = 0;
102 }
103
104 static const gchar*
105 clutter_text_buffer_normal_get_text (ClutterTextBuffer *buffer,
106                                   gsize          *n_bytes)
107 {
108   if (n_bytes)
109     *n_bytes = buffer->priv->normal_text_bytes;
110   if (!buffer->priv->normal_text)
111       return "";
112   return buffer->priv->normal_text;
113 }
114
115 static guint
116 clutter_text_buffer_normal_get_length (ClutterTextBuffer *buffer)
117 {
118   return buffer->priv->normal_text_chars;
119 }
120
121 static guint
122 clutter_text_buffer_normal_insert_text (ClutterTextBuffer *buffer,
123                                      guint           position,
124                                      const gchar    *chars,
125                                      guint           n_chars)
126 {
127   ClutterTextBufferPrivate *pv = buffer->priv;
128   gsize prev_size;
129   gsize n_bytes;
130   gsize at;
131
132   n_bytes = g_utf8_offset_to_pointer (chars, n_chars) - chars;
133
134   /* Need more memory */
135   if (n_bytes + pv->normal_text_bytes + 1 > pv->normal_text_size)
136     {
137       gchar *et_new;
138
139       prev_size = pv->normal_text_size;
140
141       /* Calculate our new buffer size */
142       while (n_bytes + pv->normal_text_bytes + 1 > pv->normal_text_size)
143         {
144           if (pv->normal_text_size == 0)
145             pv->normal_text_size = MIN_SIZE;
146           else
147             {
148               if (2 * pv->normal_text_size < CLUTTER_TEXT_BUFFER_MAX_SIZE)
149                 pv->normal_text_size *= 2;
150               else
151                 {
152                   pv->normal_text_size = CLUTTER_TEXT_BUFFER_MAX_SIZE;
153                   if (n_bytes > pv->normal_text_size - pv->normal_text_bytes - 1)
154                     {
155                       n_bytes = pv->normal_text_size - pv->normal_text_bytes - 1;
156                       n_bytes = g_utf8_find_prev_char (chars, chars + n_bytes + 1) - chars;
157                       n_chars = g_utf8_strlen (chars, n_bytes);
158                     }
159                   break;
160                 }
161             }
162         }
163
164       /* Could be a password, so can't leave stuff in memory. */
165       et_new = g_malloc (pv->normal_text_size);
166       memcpy (et_new, pv->normal_text, MIN (prev_size, pv->normal_text_size));
167       trash_area (pv->normal_text, prev_size);
168       g_free (pv->normal_text);
169       pv->normal_text = et_new;
170     }
171
172   /* Actual text insertion */
173   at = g_utf8_offset_to_pointer (pv->normal_text, position) - pv->normal_text;
174   g_memmove (pv->normal_text + at + n_bytes, pv->normal_text + at, pv->normal_text_bytes - at);
175   memcpy (pv->normal_text + at, chars, n_bytes);
176
177   /* Book keeping */
178   pv->normal_text_bytes += n_bytes;
179   pv->normal_text_chars += n_chars;
180   pv->normal_text[pv->normal_text_bytes] = '\0';
181
182   clutter_text_buffer_emit_inserted_text (buffer, position, chars, n_chars);
183   return n_chars;
184 }
185
186 static guint
187 clutter_text_buffer_normal_delete_text (ClutterTextBuffer *buffer,
188                                      guint           position,
189                                      guint           n_chars)
190 {
191   ClutterTextBufferPrivate *pv = buffer->priv;
192   gsize start, end;
193
194   if (position > pv->normal_text_chars)
195     position = pv->normal_text_chars;
196   if (position + n_chars > pv->normal_text_chars)
197     n_chars = pv->normal_text_chars - position;
198
199   if (n_chars > 0)
200     {
201       start = g_utf8_offset_to_pointer (pv->normal_text, position) - pv->normal_text;
202       end = g_utf8_offset_to_pointer (pv->normal_text, position + n_chars) - pv->normal_text;
203
204       g_memmove (pv->normal_text + start, pv->normal_text + end, pv->normal_text_bytes + 1 - end);
205       pv->normal_text_chars -= n_chars;
206       pv->normal_text_bytes -= (end - start);
207
208       /*
209        * Could be a password, make sure we don't leave anything sensitive after
210        * the terminating zero.  Note, that the terminating zero already trashed
211        * one byte.
212        */
213       trash_area (pv->normal_text + pv->normal_text_bytes + 1, end - start - 1);
214
215       clutter_text_buffer_emit_deleted_text (buffer, position, n_chars);
216     }
217
218   return n_chars;
219 }
220
221 /* --------------------------------------------------------------------------------
222  *
223  */
224
225 static void
226 clutter_text_buffer_real_inserted_text (ClutterTextBuffer *buffer,
227                                      guint           position,
228                                      const gchar    *chars,
229                                      guint           n_chars)
230 {
231   g_object_notify (G_OBJECT (buffer), "text");
232   g_object_notify (G_OBJECT (buffer), "length");
233 }
234
235 static void
236 clutter_text_buffer_real_deleted_text (ClutterTextBuffer *buffer,
237                                     guint           position,
238                                     guint           n_chars)
239 {
240   g_object_notify (G_OBJECT (buffer), "text");
241   g_object_notify (G_OBJECT (buffer), "length");
242 }
243
244 /* --------------------------------------------------------------------------------
245  *
246  */
247
248 static void
249 clutter_text_buffer_init (ClutterTextBuffer *buffer)
250 {
251   ClutterTextBufferPrivate *pv;
252
253   pv = buffer->priv = G_TYPE_INSTANCE_GET_PRIVATE (buffer, CLUTTER_TYPE_TEXT_BUFFER, ClutterTextBufferPrivate);
254
255   pv->normal_text = NULL;
256   pv->normal_text_chars = 0;
257   pv->normal_text_bytes = 0;
258   pv->normal_text_size = 0;
259 }
260
261 static void
262 clutter_text_buffer_finalize (GObject *obj)
263 {
264   ClutterTextBuffer *buffer = CLUTTER_TEXT_BUFFER (obj);
265   ClutterTextBufferPrivate *pv = buffer->priv;
266
267   if (pv->normal_text)
268     {
269       trash_area (pv->normal_text, pv->normal_text_size);
270       g_free (pv->normal_text);
271       pv->normal_text = NULL;
272       pv->normal_text_bytes = pv->normal_text_size = 0;
273       pv->normal_text_chars = 0;
274     }
275
276   G_OBJECT_CLASS (clutter_text_buffer_parent_class)->finalize (obj);
277 }
278
279 static void
280 clutter_text_buffer_set_property (GObject      *obj,
281                                   guint         prop_id,
282                                   const GValue *value,
283                                   GParamSpec   *pspec)
284 {
285   ClutterTextBuffer *buffer = CLUTTER_TEXT_BUFFER (obj);
286
287   switch (prop_id)
288     {
289     case PROP_MAX_LENGTH:
290       clutter_text_buffer_set_max_length (buffer, g_value_get_int (value));
291       break;
292     default:
293       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
294       break;
295     }
296 }
297
298 static void
299 clutter_text_buffer_get_property (GObject    *obj,
300                                   guint       prop_id,
301                                   GValue     *value,
302                                   GParamSpec *pspec)
303 {
304   ClutterTextBuffer *buffer = CLUTTER_TEXT_BUFFER (obj);
305
306   switch (prop_id)
307     {
308     case PROP_TEXT:
309       g_value_set_string (value, clutter_text_buffer_get_text (buffer));
310       break;
311     case PROP_LENGTH:
312       g_value_set_uint (value, clutter_text_buffer_get_length (buffer));
313       break;
314     case PROP_MAX_LENGTH:
315       g_value_set_int (value, clutter_text_buffer_get_max_length (buffer));
316       break;
317     default:
318       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
319       break;
320     }
321 }
322
323 static void
324 clutter_text_buffer_class_init (ClutterTextBufferClass *klass)
325 {
326   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
327
328   gobject_class->finalize = clutter_text_buffer_finalize;
329   gobject_class->set_property = clutter_text_buffer_set_property;
330   gobject_class->get_property = clutter_text_buffer_get_property;
331
332   klass->get_text = clutter_text_buffer_normal_get_text;
333   klass->get_length = clutter_text_buffer_normal_get_length;
334   klass->insert_text = clutter_text_buffer_normal_insert_text;
335   klass->delete_text = clutter_text_buffer_normal_delete_text;
336
337   klass->inserted_text = clutter_text_buffer_real_inserted_text;
338   klass->deleted_text = clutter_text_buffer_real_deleted_text;
339
340   g_type_class_add_private (gobject_class, sizeof (ClutterTextBufferPrivate));
341
342   /**
343    * ClutterTextBuffer:text:
344    *
345    * The contents of the buffer.
346    *
347    * Since: 1.10
348    */
349   obj_props[PROP_TEXT] =
350       g_param_spec_string ("text",
351                            P_("Text"),
352                            P_("The contents of the buffer"),
353                            "",
354                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
355
356   /**
357    * ClutterTextBuffer:length:
358    *
359    * The length (in characters) of the text in buffer.
360    *
361    * Since: 1.10
362    */
363   obj_props[PROP_LENGTH] =
364       g_param_spec_uint ("length",
365                          P_("Text length"),
366                          P_("Length of the text currently in the buffer"),
367                          0, CLUTTER_TEXT_BUFFER_MAX_SIZE, 0,
368                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
369
370   /**
371    * ClutterTextBuffer:max-length:
372    *
373    * The maximum length (in characters) of the text in the buffer.
374    *
375    * Since: 1.10
376    */
377   obj_props[PROP_MAX_LENGTH] =
378       g_param_spec_int ("max-length",
379                         P_("Maximum length"),
380                         P_("Maximum number of characters for this entry. Zero if no maximum"),
381                         0, CLUTTER_TEXT_BUFFER_MAX_SIZE, 0,
382                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
383
384   g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
385
386   /**
387    * ClutterTextBuffer::inserted-text:
388    * @buffer: a #ClutterTextBuffer
389    * @position: the position the text was inserted at.
390    * @chars: The text that was inserted.
391    * @n_chars: The number of characters that were inserted.
392    *
393    * This signal is emitted after text is inserted into the buffer.
394    *
395    * Since: 1.10
396    */
397   signals[INSERTED_TEXT] =
398     g_signal_new (I_("inserted-text"),
399                   CLUTTER_TYPE_TEXT_BUFFER,
400                   G_SIGNAL_RUN_FIRST,
401                   G_STRUCT_OFFSET (ClutterTextBufferClass, inserted_text),
402                   NULL, NULL,
403                   _clutter_marshal_VOID__UINT_STRING_UINT,
404                   G_TYPE_NONE, 3,
405                   G_TYPE_UINT,
406                   G_TYPE_STRING,
407                   G_TYPE_UINT);
408
409   /**
410    * ClutterTextBuffer::deleted-text:
411    * @buffer: a #ClutterTextBuffer
412    * @position: the position the text was deleted at.
413    * @n_chars: The number of characters that were deleted.
414    *
415    * This signal is emitted after text is deleted from the buffer.
416    *
417    * Since: 1.10
418    */
419   signals[DELETED_TEXT] =
420     g_signal_new (I_("deleted-text"),
421                   CLUTTER_TYPE_TEXT_BUFFER,
422                   G_SIGNAL_RUN_FIRST,
423                   G_STRUCT_OFFSET (ClutterTextBufferClass, deleted_text),
424                   NULL, NULL,
425                   _clutter_marshal_VOID__UINT_UINT,
426                   G_TYPE_NONE, 2,
427                   G_TYPE_UINT,
428                   G_TYPE_UINT);
429 }
430
431 /* --------------------------------------------------------------------------------
432  *
433  */
434
435 /**
436  * clutter_text_buffer_new:
437  *
438  * Create a new ClutterTextBuffer object.
439  *
440  * Return value: A new ClutterTextBuffer object.
441  *
442  * Since: 1.10
443  **/
444 ClutterTextBuffer*
445 clutter_text_buffer_new (void)
446 {
447   return g_object_new (CLUTTER_TYPE_TEXT_BUFFER, NULL);
448 }
449
450
451 /**
452  * clutter_text_buffer_new_with_text:
453  * @text: (allow-none): initial buffer text
454  * @text_len: initial buffer text length, or -1 for null-terminated.
455  *
456  * Create a new ClutterTextBuffer object with some text.
457  *
458  * Return value: A new ClutterTextBuffer object.
459  *
460  * Since: 1.10
461  **/
462 ClutterTextBuffer*
463 clutter_text_buffer_new_with_text (const gchar   *text,
464                                    gssize         text_len)
465 {
466   ClutterTextBuffer *buffer;
467   buffer = clutter_text_buffer_new ();
468   clutter_text_buffer_set_text (buffer, text, text_len);
469   return buffer;
470 }
471
472
473 /**
474  * clutter_text_buffer_get_length:
475  * @buffer: a #ClutterTextBuffer
476  *
477  * Retrieves the length in characters of the buffer.
478  *
479  * Return value: The number of characters in the buffer.
480  *
481  * Since: 1.10
482  **/
483 guint
484 clutter_text_buffer_get_length (ClutterTextBuffer *buffer)
485 {
486   ClutterTextBufferClass *klass;
487
488   g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
489
490   klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
491   g_return_val_if_fail (klass->get_length != NULL, 0);
492
493   return (*klass->get_length) (buffer);
494 }
495
496 /**
497  * clutter_text_buffer_get_bytes:
498  * @buffer: a #ClutterTextBuffer
499  *
500  * Retrieves the length in bytes of the buffer.
501  * See clutter_text_buffer_get_length().
502  *
503  * Return value: The byte length of the buffer.
504  *
505  * Since: 1.10
506  **/
507 gsize
508 clutter_text_buffer_get_bytes (ClutterTextBuffer *buffer)
509 {
510   ClutterTextBufferClass *klass;
511   gsize bytes = 0;
512
513   g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
514
515   klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
516   g_return_val_if_fail (klass->get_text != NULL, 0);
517
518   (*klass->get_text) (buffer, &bytes);
519   return bytes;
520 }
521
522 /**
523  * clutter_text_buffer_get_text:
524  * @buffer: a #ClutterTextBuffer
525  *
526  * Retrieves the contents of the buffer.
527  *
528  * The memory pointer returned by this call will not change
529  * unless this object emits a signal, or is finalized.
530  *
531  * Return value: a pointer to the contents of the widget as a
532  *      string. This string points to internally allocated
533  *      storage in the buffer and must not be freed, modified or
534  *      stored.
535  *
536  * Since: 1.10
537  **/
538 const gchar*
539 clutter_text_buffer_get_text (ClutterTextBuffer *buffer)
540 {
541   ClutterTextBufferClass *klass;
542
543   g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), NULL);
544
545   klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
546   g_return_val_if_fail (klass->get_text != NULL, NULL);
547
548   return (*klass->get_text) (buffer, NULL);
549 }
550
551 /**
552  * clutter_text_buffer_set_text:
553  * @buffer: a #ClutterTextBuffer
554  * @chars: the new text
555  * @n_chars: the number of characters in @text, or -1
556  *
557  * Sets the text in the buffer.
558  *
559  * This is roughly equivalent to calling clutter_text_buffer_delete_text()
560  * and clutter_text_buffer_insert_text().
561  *
562  * Note that @n_chars is in characters, not in bytes.
563  *
564  * Since: 1.10
565  **/
566 void
567 clutter_text_buffer_set_text (ClutterTextBuffer *buffer,
568                               const gchar       *chars,
569                               gint               n_chars)
570 {
571   g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
572   g_return_if_fail (chars != NULL);
573
574   g_object_freeze_notify (G_OBJECT (buffer));
575   clutter_text_buffer_delete_text (buffer, 0, -1);
576   clutter_text_buffer_insert_text (buffer, 0, chars, n_chars);
577   g_object_thaw_notify (G_OBJECT (buffer));
578 }
579
580 /**
581  * clutter_text_buffer_set_max_length:
582  * @buffer: a #ClutterTextBuffer
583  * @max_length: the maximum length of the entry buffer, or 0 for no maximum.
584  *   (other than the maximum length of entries.) The value passed in will
585  *   be clamped to the range [ 0, %CLUTTER_TEXT_BUFFER_MAX_SIZE ].
586  *
587  * Sets the maximum allowed length of the contents of the buffer. If
588  * the current contents are longer than the given length, then they
589  * will be truncated to fit.
590  *
591  * Since: 1.10
592  **/
593 void
594 clutter_text_buffer_set_max_length (ClutterTextBuffer *buffer,
595                                     gint               max_length)
596 {
597   g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
598
599   max_length = CLAMP (max_length, 0, CLUTTER_TEXT_BUFFER_MAX_SIZE);
600
601   if (max_length > 0 && clutter_text_buffer_get_length (buffer) > max_length)
602     clutter_text_buffer_delete_text (buffer, max_length, -1);
603
604   buffer->priv->max_length = max_length;
605   g_object_notify (G_OBJECT (buffer), "max-length");
606 }
607
608 /**
609  * clutter_text_buffer_get_max_length:
610  * @buffer: a #ClutterTextBuffer
611  *
612  * Retrieves the maximum allowed length of the text in
613  * @buffer. See clutter_text_buffer_set_max_length().
614  *
615  * Return value: the maximum allowed number of characters
616  *               in #ClutterTextBuffer, or 0 if there is no maximum.
617  *
618  * Since: 1.10
619  */
620 gint
621 clutter_text_buffer_get_max_length (ClutterTextBuffer *buffer)
622 {
623   g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
624   return buffer->priv->max_length;
625 }
626
627 /**
628  * clutter_text_buffer_insert_text:
629  * @buffer: a #ClutterTextBuffer
630  * @position: the position at which to insert text.
631  * @chars: the text to insert into the buffer.
632  * @n_chars: the length of the text in characters, or -1
633  *
634  * Inserts @n_chars characters of @chars into the contents of the
635  * buffer, at position @position.
636  *
637  * If @n_chars is negative, then characters from chars will be inserted
638  * until a null-terminator is found. If @position or @n_chars are out of
639  * bounds, or the maximum buffer text length is exceeded, then they are
640  * coerced to sane values.
641  *
642  * Note that the position and length are in characters, not in bytes.
643  *
644  * Returns: The number of characters actually inserted.
645  *
646  * Since: 1.10
647  */
648 guint
649 clutter_text_buffer_insert_text (ClutterTextBuffer *buffer,
650                                  guint              position,
651                                  const gchar       *chars,
652                                  gint               n_chars)
653 {
654   ClutterTextBufferClass *klass;
655   ClutterTextBufferPrivate *pv;
656   guint length;
657
658   g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
659
660   length = clutter_text_buffer_get_length (buffer);
661   pv = buffer->priv;
662
663   if (n_chars < 0)
664     n_chars = g_utf8_strlen (chars, -1);
665
666   /* Bring position into bounds */
667   if (position > length)
668     position = length;
669
670   /* Make sure not entering too much data */
671   if (pv->max_length > 0)
672     {
673       if (length >= pv->max_length)
674         n_chars = 0;
675       else if (length + n_chars > pv->max_length)
676         n_chars -= (length + n_chars) - pv->max_length;
677     }
678
679   klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
680   g_return_val_if_fail (klass->insert_text != NULL, 0);
681
682   return (klass->insert_text) (buffer, position, chars, n_chars);
683 }
684
685 /**
686  * clutter_text_buffer_delete_text:
687  * @buffer: a #ClutterTextBuffer
688  * @position: position at which to delete text
689  * @n_chars: number of characters to delete
690  *
691  * Deletes a sequence of characters from the buffer. @n_chars characters are
692  * deleted starting at @position. If @n_chars is negative, then all characters
693  * until the end of the text are deleted.
694  *
695  * If @position or @n_chars are out of bounds, then they are coerced to sane
696  * values.
697  *
698  * Note that the positions are specified in characters, not bytes.
699  *
700  * Returns: The number of characters deleted.
701  *
702  * Since: 1.10
703  */
704 guint
705 clutter_text_buffer_delete_text (ClutterTextBuffer *buffer,
706                                  guint              position,
707                                  gint               n_chars)
708 {
709   ClutterTextBufferClass *klass;
710   guint length;
711
712   g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), 0);
713
714   length = clutter_text_buffer_get_length (buffer);
715   if (n_chars < 0)
716     n_chars = length;
717   if (position > length)
718     position = length;
719   if (position + n_chars > length)
720     n_chars = length - position;
721
722   klass = CLUTTER_TEXT_BUFFER_GET_CLASS (buffer);
723   g_return_val_if_fail (klass->delete_text != NULL, 0);
724
725   return (klass->delete_text) (buffer, position, n_chars);
726 }
727
728 /**
729  * clutter_text_buffer_emit_inserted_text:
730  * @buffer: a #ClutterTextBuffer
731  * @position: position at which text was inserted
732  * @chars: text that was inserted
733  * @n_chars: number of characters inserted
734  *
735  * Emits the #ClutterTextBuffer::inserted-text signal on @buffer.
736  *
737  * Used when subclassing #ClutterTextBuffer
738  *
739  * Since: 1.10
740  */
741 void
742 clutter_text_buffer_emit_inserted_text (ClutterTextBuffer *buffer,
743                                         guint              position,
744                                         const gchar       *chars,
745                                         guint              n_chars)
746 {
747   g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
748   g_signal_emit (buffer, signals[INSERTED_TEXT], 0, position, chars, n_chars);
749 }
750
751 /**
752  * clutter_text_buffer_emit_deleted_text:
753  * @buffer: a #ClutterTextBuffer
754  * @position: position at which text was deleted
755  * @n_chars: number of characters deleted
756  *
757  * Emits the #ClutterTextBuffer::deleted-text signal on @buffer.
758  *
759  * Used when subclassing #ClutterTextBuffer
760  *
761  * Since: 1.10
762  */
763 void
764 clutter_text_buffer_emit_deleted_text (ClutterTextBuffer *buffer,
765                                        guint              position,
766                                        guint              n_chars)
767 {
768   g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer));
769   g_signal_emit (buffer, signals[DELETED_TEXT], 0, position, n_chars);
770 }