[text] Add pre-edit string to ClutterText

Input Methods require to be able to set a "pre-edit string", that is
a string that it's just displayed into the Text actor without being
committed to the actor's buffer. The string might require custom Pango
attributes, and an update of the cursor position.
This commit is contained in:
Emmanuele Bassi 2009-09-09 12:15:23 +01:00 committed by Emmanuele Bassi
parent 2cc88f1140
commit 2883728387
4 changed files with 346 additions and 32 deletions

View File

@ -104,6 +104,7 @@ struct _ClutterTextPrivate
gchar *text; gchar *text;
gchar *font_name; gchar *font_name;
gchar *preedit_str;
ClutterColor text_color; ClutterColor text_color;
@ -112,6 +113,7 @@ struct _ClutterTextPrivate
PangoAttrList *attrs; PangoAttrList *attrs;
PangoAttrList *effective_attrs; PangoAttrList *effective_attrs;
PangoAttrList *preedit_attrs;
guint alignment : 2; guint alignment : 2;
guint wrap : 1; guint wrap : 1;
@ -128,6 +130,7 @@ struct _ClutterTextPrivate
guint selection_color_set : 1; guint selection_color_set : 1;
guint in_select_drag : 1; guint in_select_drag : 1;
guint cursor_color_set : 1; guint cursor_color_set : 1;
guint preedit_set : 1;
/* current cursor position */ /* current cursor position */
gint position; gint position;
@ -157,6 +160,9 @@ struct _ClutterTextPrivate
ClutterColor cursor_color; ClutterColor cursor_color;
guint cursor_size; guint cursor_size;
guint preedit_cursor_pos;
gint preedit_n_chars;
ClutterColor selection_color; ClutterColor selection_color;
gint max_length; gint max_length;
@ -242,6 +248,39 @@ clutter_text_clear_selection (ClutterText *self)
} }
} }
static gchar *
clutter_text_get_display_text (ClutterText *self)
{
ClutterTextPrivate *priv = self->priv;
if (priv->text == NULL)
return g_strdup ("");
if (G_LIKELY (priv->password_char == 0))
return g_strndup (priv->text, priv->n_bytes);
else
{
GString *str = g_string_sized_new (priv->n_bytes);
gunichar invisible_char;
gchar buf[7];
gint char_len, i;
invisible_char = priv->password_char;
/* we need to convert the string built of invisible
* characters into UTF-8 for it to be fed to the Pango
* layout
*/
memset (buf, 0, sizeof (buf));
char_len = g_unichar_to_utf8 (invisible_char, buf);
for (i = 0; i < priv->n_chars; i++)
g_string_append_len (str, buf, char_len);
return g_string_free (str, FALSE);
}
}
static PangoLayout * static PangoLayout *
clutter_text_create_layout_no_cache (ClutterText *text, clutter_text_create_layout_no_cache (ClutterText *text,
gfloat allocation_width, gfloat allocation_width,
@ -249,38 +288,41 @@ clutter_text_create_layout_no_cache (ClutterText *text,
{ {
ClutterTextPrivate *priv = text->priv; ClutterTextPrivate *priv = text->priv;
PangoLayout *layout; PangoLayout *layout;
gchar *contents;
gsize contents_len;
layout = clutter_actor_create_pango_layout (CLUTTER_ACTOR (text), NULL); layout = clutter_actor_create_pango_layout (CLUTTER_ACTOR (text), NULL);
pango_layout_set_font_description (layout, priv->font_desc); pango_layout_set_font_description (layout, priv->font_desc);
if (priv->text) contents = clutter_text_get_display_text (text);
contents_len = strlen (contents);
if (priv->editable && priv->preedit_set)
{ {
if (G_LIKELY (priv->password_char == 0)) GString *tmp = g_string_new (contents);
pango_layout_set_text (layout, priv->text, priv->n_bytes); PangoAttrList *tmp_attrs = pango_attr_list_new ();
gint cursor_index;
if (priv->position == 0)
cursor_index = 0;
else else
{ cursor_index = offset_to_bytes (contents, priv->position);
GString *str = g_string_sized_new (priv->n_bytes);
gunichar invisible_char;
gchar buf[7];
gint char_len, i;
invisible_char = priv->password_char; g_string_insert (tmp, cursor_index, priv->preedit_str);
/* we need to convert the string built of invisible pango_layout_set_text (layout, tmp->str, tmp->len);
* characters into UTF-8 for it to be fed to the Pango
* layout
*/
memset (buf, 0, sizeof (buf));
char_len = g_unichar_to_utf8 (invisible_char, buf);
for (i = 0; i < priv->n_chars; i++) pango_attr_list_splice (tmp_attrs, priv->preedit_attrs,
g_string_append_len (str, buf, char_len); cursor_index,
strlen (priv->preedit_str));
pango_layout_set_text (layout, str->str, str->len); pango_layout_set_attributes (layout, tmp_attrs);
g_string_free (str, TRUE); g_string_free (tmp, TRUE);
} pango_attr_list_unref (tmp_attrs);
} }
else
pango_layout_set_text (layout, contents, contents_len);
if (!priv->editable && priv->effective_attrs) if (!priv->editable && priv->effective_attrs)
pango_layout_set_attributes (layout, priv->effective_attrs); pango_layout_set_attributes (layout, priv->effective_attrs);
@ -355,6 +397,8 @@ clutter_text_create_layout_no_cache (ClutterText *text,
pango_layout_set_height (layout, height); pango_layout_set_height (layout, height);
} }
g_free (contents);
return layout; return layout;
} }
@ -505,6 +549,7 @@ clutter_text_position_to_coords (ClutterText *self,
{ {
ClutterTextPrivate *priv; ClutterTextPrivate *priv;
PangoRectangle rect; PangoRectangle rect;
gint n_chars;
gint password_char_bytes = 1; gint password_char_bytes = 1;
gint index_; gint index_;
@ -512,7 +557,12 @@ clutter_text_position_to_coords (ClutterText *self,
priv = self->priv; priv = self->priv;
if (position < -1 || position > priv->n_chars) if (priv->preedit_set)
n_chars = priv->n_chars + priv->preedit_n_chars;
else
n_chars = priv->n_chars;
if (position < -1 || position > n_chars)
return FALSE; return FALSE;
if (priv->password_char != 0) if (priv->password_char != 0)
@ -523,7 +573,7 @@ clutter_text_position_to_coords (ClutterText *self,
if (priv->password_char == 0) if (priv->password_char == 0)
index_ = priv->n_bytes; index_ = priv->n_bytes;
else else
index_ = priv->n_chars * password_char_bytes; index_ = n_chars * password_char_bytes;
} }
else if (position == 0) else if (position == 0)
{ {
@ -534,10 +584,11 @@ clutter_text_position_to_coords (ClutterText *self,
if (priv->password_char == 0) if (priv->password_char == 0)
index_ = offset_to_bytes (priv->text, position); index_ = offset_to_bytes (priv->text, position);
else else
index_ = priv->position * password_char_bytes; index_ = position * password_char_bytes;
} }
pango_layout_get_cursor_pos (clutter_text_get_layout (self), index_, pango_layout_get_cursor_pos (clutter_text_get_layout (self),
index_,
&rect, NULL); &rect, NULL);
if (x) if (x)
@ -566,9 +617,17 @@ clutter_text_ensure_cursor_position (ClutterText *self)
ClutterGeometry cursor_pos = { 0, }; ClutterGeometry cursor_pos = { 0, };
gboolean x_changed, y_changed; gboolean x_changed, y_changed;
gboolean width_changed, height_changed; gboolean width_changed, height_changed;
gint position;
position = priv->position;
CLUTTER_NOTE (MISC, "Cursor at %d (preedit %s at pos: %d)",
position,
priv->preedit_set ? "set" : "unset",
priv->preedit_set ? priv->preedit_cursor_pos : 0);
x = y = cursor_height = 0; x = y = cursor_height = 0;
clutter_text_position_to_coords (self, priv->position, clutter_text_position_to_coords (self, position,
&x, &y, &x, &y,
&cursor_height); &cursor_height);
@ -1033,11 +1092,14 @@ cursor_paint (ClutterText *self)
{ {
guint8 paint_opacity = clutter_actor_get_paint_opacity (actor); guint8 paint_opacity = clutter_actor_get_paint_opacity (actor);
const ClutterColor *color; const ClutterColor *color;
gint position;
if (priv->position == 0) position = priv->position;
if (position == 0)
priv->cursor_pos.x -= priv->cursor_size; priv->cursor_pos.x -= priv->cursor_size;
if (priv->position == priv->selection_bound) if (position == priv->selection_bound)
{ {
if (priv->cursor_color_set) if (priv->cursor_color_set)
color = &priv->cursor_color; color = &priv->cursor_color;
@ -1055,12 +1117,11 @@ cursor_paint (ClutterText *self)
priv->cursor_pos.y, priv->cursor_pos.y,
priv->cursor_pos.x + priv->cursor_pos.width, priv->cursor_pos.x + priv->cursor_pos.width,
priv->cursor_pos.y + priv->cursor_pos.height); priv->cursor_pos.y + priv->cursor_pos.height);
} }
else else
{ {
PangoLayout *layout = clutter_text_get_layout (self); PangoLayout *layout = clutter_text_get_layout (self);
const gchar *utf8 = priv->text; gchar *utf8 = clutter_text_get_display_text (self);
gint lines; gint lines;
gint start_index; gint start_index;
gint end_index; gint end_index;
@ -1080,10 +1141,10 @@ cursor_paint (ClutterText *self)
* color->alpha * color->alpha
/ 255); / 255);
if (priv->position == 0) if (position == 0)
start_index = 0; start_index = 0;
else else
start_index = offset_to_bytes (utf8, priv->position); start_index = offset_to_bytes (utf8, position);
if (priv->selection_bound == 0) if (priv->selection_bound == 0)
end_index = 0; end_index = 0;
@ -1146,6 +1207,8 @@ cursor_paint (ClutterText *self)
g_free (ranges); g_free (ranges);
} }
g_free (utf8);
} }
} }
} }
@ -1554,7 +1617,6 @@ clutter_text_paint (ClutterActor *self)
* scrolling */ * scrolling */
priv->text_x = text_x; priv->text_x = text_x;
clutter_text_ensure_cursor_position (text); clutter_text_ensure_cursor_position (text);
} }
else else
{ {
@ -2606,6 +2668,7 @@ clutter_text_init (ClutterText *self)
priv->selection_color_set = FALSE; priv->selection_color_set = FALSE;
priv->cursor_color_set = FALSE; priv->cursor_color_set = FALSE;
priv->preedit_set = FALSE;
priv->password_char = 0; priv->password_char = 0;
@ -4383,3 +4446,69 @@ clutter_text_get_single_line_mode (ClutterText *self)
return self->priv->single_line_mode; return self->priv->single_line_mode;
} }
/**
* clutter_text_set_preedit_string:
* @self: a #ClutterText
* @preedit_str: (allow-none): the pre-edit string, or %NULL to unset it
* @preedit_attrs: (allow-none): the pre-edit string attributes
* @cursor_pos: the cursor position for the pre-edit string
*
* Sets, or unsets, the pre-edit string. This function is useful
* for input methods to display a string (with eventual specific
* Pango attributes) before it is entered inside the #ClutterText
* buffer.
*
* The preedit string and attributes are ignored if the #ClutterText
* actor is not editable.
*
* This function should not be used by applications
*
* Since: 1.2
*/
void
clutter_text_set_preedit_string (ClutterText *self,
const gchar *preedit_str,
PangoAttrList *preedit_attrs,
guint cursor_pos)
{
ClutterTextPrivate *priv;
g_return_if_fail (CLUTTER_IS_TEXT (self));
priv = self->priv;
g_free (priv->preedit_str);
if (priv->preedit_attrs != NULL)
{
pango_attr_list_unref (priv->preedit_attrs);
priv->preedit_attrs = NULL;
}
priv->preedit_n_chars = 0;
priv->preedit_cursor_pos = 0;
if (preedit_str == NULL || *preedit_str == '\0')
priv->preedit_set = FALSE;
else
{
priv->preedit_str = g_strdup (preedit_str);
if (priv->preedit_str != NULL)
priv->preedit_n_chars = g_utf8_strlen (priv->preedit_str, -1);
else
priv->preedit_n_chars = 0;
if (preedit_attrs != NULL)
priv->preedit_attrs = pango_attr_list_ref (preedit_attrs);
priv->preedit_cursor_pos =
CLAMP (cursor_pos, 0, priv->preedit_n_chars);
priv->preedit_set = TRUE;
}
clutter_text_dirty_cache (self);
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
}

View File

@ -204,6 +204,11 @@ gboolean clutter_text_position_to_coords (ClutterText *sel
gfloat *y, gfloat *y,
gfloat *line_height); gfloat *line_height);
void clutter_text_set_preedit_string (ClutterText *self,
const gchar *preedit_str,
PangoAttrList *preedit_attr,
guint cursor_position);
G_END_DECLS G_END_DECLS
#endif /* __CLUTTER_TEXT_H__ */ #endif /* __CLUTTER_TEXT_H__ */

View File

@ -1604,6 +1604,7 @@ clutter_text_get_cursor_size
<SUBSECTION> <SUBSECTION>
clutter_text_activate clutter_text_activate
clutter_text_position_to_coords clutter_text_position_to_coords
clutter_text_set_preedit_string
<SUBSECTION Standard> <SUBSECTION Standard>
CLUTTER_IS_TEXT CLUTTER_IS_TEXT

View File

@ -30,6 +30,182 @@ on_entry_activate (ClutterText *text,
clutter_text_get_selection_bound (text)); clutter_text_get_selection_bound (text));
} }
#define is_hex_digit(c) (((c) >= '0' && (c) <= '9') || \
((c) >= 'a' && (c) <= 'f') || \
((c) >= 'A' && (c) <= 'F'))
#define to_hex_digit(c) (((c) <= '9') ? (c) - '0' : ((c) & 7) + 9)
static gboolean
on_captured_event (ClutterText *text,
ClutterEvent *event,
gpointer dummy G_GNUC_UNUSED)
{
gboolean is_unicode_mode = FALSE;
gunichar c;
guint keyval;
if (event->type != CLUTTER_KEY_PRESS)
return FALSE;
is_unicode_mode = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (text),
"unicode-mode"));
c = clutter_event_get_key_unicode (event);
keyval = clutter_event_get_key_symbol (event);
if (keyval == CLUTTER_u)
{
ClutterModifierType mods = clutter_event_get_state (event);
if (is_unicode_mode)
{
GString *str = g_object_get_data (G_OBJECT (text), "unicode-str");
clutter_text_set_preedit_string (text, NULL, NULL, 0);
g_object_set_data (G_OBJECT (text), "unicode-mode",
GINT_TO_POINTER (FALSE));
g_object_set_data (G_OBJECT (text), "unicode-str",
NULL);
g_string_free (str, TRUE);
return FALSE;
}
if ((mods & CLUTTER_CONTROL_MASK) &&
(mods & CLUTTER_SHIFT_MASK))
{
PangoAttrList *attrs;
PangoAttribute *a;
GString *str = g_string_sized_new (5);
g_string_append (str, "u");
g_object_set_data (G_OBJECT (text),
"unicode-mode",
GINT_TO_POINTER (TRUE));
g_object_set_data (G_OBJECT (text),
"unicode-str",
str);
attrs = pango_attr_list_new ();
a = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
a->start_index = 0;
a->end_index = str->len;
pango_attr_list_insert (attrs, a);
clutter_text_set_preedit_string (text, str->str, attrs, str->len);
pango_attr_list_unref (attrs);
return TRUE;
}
return FALSE;
}
else if (is_unicode_mode && is_hex_digit (c))
{
GString *str = g_object_get_data (G_OBJECT (text), "unicode-str");
PangoAttrList *attrs;
PangoAttribute *a;
gchar buf[8];
gsize len;
len = g_unichar_to_utf8 (c, buf);
buf[len] = '\0';
g_string_append (str, buf);
g_debug ("added '%s' to '%s' (len:%d)", buf, str->str, str->len);
attrs = pango_attr_list_new ();
a = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
a->start_index = 0;
a->end_index = str->len;
pango_attr_list_insert (attrs, a);
clutter_text_set_preedit_string (text, str->str, attrs, str->len);
pango_attr_list_unref (attrs);
return TRUE;
}
else if (is_unicode_mode && (keyval == CLUTTER_BackSpace))
{
GString *str = g_object_get_data (G_OBJECT (text), "unicode-str");
PangoAttrList *attrs;
PangoAttribute *a;
g_string_truncate (str, str->len - 1);
attrs = pango_attr_list_new ();
a = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
a->start_index = 0;
a->end_index = str->len;
pango_attr_list_insert (attrs, a);
clutter_text_set_preedit_string (text, str->str, attrs, str->len);
pango_attr_list_unref (attrs);
return TRUE;
}
else if (is_unicode_mode &&
(keyval == CLUTTER_Return ||
keyval == CLUTTER_KP_Enter ||
keyval == CLUTTER_ISO_Enter ||
keyval == CLUTTER_KP_Space))
{
GString *str = g_object_get_data (G_OBJECT (text), "unicode-str");
const gchar *contents = clutter_text_get_text (text);
gunichar uchar = 0;
gchar ch;
gint i;
clutter_text_set_preedit_string (text, NULL, NULL, 0);
g_object_set_data (G_OBJECT (text), "unicode-mode",
GINT_TO_POINTER (FALSE));
g_object_set_data (G_OBJECT (text), "unicode-str",
NULL);
for (i = 0; i < str->len; i++)
{
ch = str->str[i];
if (is_hex_digit (ch))
uchar += ((gunichar) to_hex_digit (ch) << ((4 - i) * 4));
}
g_assert (g_unichar_validate (uchar));
g_string_overwrite (str, 0, contents);
g_string_insert_unichar (str,
clutter_text_get_cursor_position (text),
uchar);
i = clutter_text_get_cursor_position (text);
clutter_text_set_text (text, str->str);
if (i >= 0)
i += 1;
else
i = -1;
clutter_text_set_cursor_position (text, i);
clutter_text_set_selection_bound (text, i);
g_string_free (str, TRUE);
return TRUE;
}
else
return FALSE;
}
static ClutterActor * static ClutterActor *
create_label (const ClutterColor *color, create_label (const ClutterColor *color,
const gchar *text) const gchar *text)
@ -77,6 +253,9 @@ create_entry (const ClutterColor *color,
g_signal_connect (retval, "paint", g_signal_connect (retval, "paint",
G_CALLBACK (on_entry_paint), G_CALLBACK (on_entry_paint),
NULL); NULL);
g_signal_connect (retval, "captured-event",
G_CALLBACK (on_captured_event),
NULL);
return retval; return retval;
} }