[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 *font_name;
gchar *preedit_str;
ClutterColor text_color;
@ -112,6 +113,7 @@ struct _ClutterTextPrivate
PangoAttrList *attrs;
PangoAttrList *effective_attrs;
PangoAttrList *preedit_attrs;
guint alignment : 2;
guint wrap : 1;
@ -128,6 +130,7 @@ struct _ClutterTextPrivate
guint selection_color_set : 1;
guint in_select_drag : 1;
guint cursor_color_set : 1;
guint preedit_set : 1;
/* current cursor position */
gint position;
@ -157,6 +160,9 @@ struct _ClutterTextPrivate
ClutterColor cursor_color;
guint cursor_size;
guint preedit_cursor_pos;
gint preedit_n_chars;
ClutterColor selection_color;
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 *
clutter_text_create_layout_no_cache (ClutterText *text,
gfloat allocation_width,
@ -249,38 +288,41 @@ clutter_text_create_layout_no_cache (ClutterText *text,
{
ClutterTextPrivate *priv = text->priv;
PangoLayout *layout;
gchar *contents;
gsize contents_len;
layout = clutter_actor_create_pango_layout (CLUTTER_ACTOR (text), NULL);
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))
pango_layout_set_text (layout, priv->text, priv->n_bytes);
GString *tmp = g_string_new (contents);
PangoAttrList *tmp_attrs = pango_attr_list_new ();
gint cursor_index;
if (priv->position == 0)
cursor_index = 0;
else
{
GString *str = g_string_sized_new (priv->n_bytes);
gunichar invisible_char;
gchar buf[7];
gint char_len, i;
cursor_index = offset_to_bytes (contents, priv->position);
invisible_char = priv->password_char;
g_string_insert (tmp, cursor_index, priv->preedit_str);
/* 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);
pango_layout_set_text (layout, tmp->str, tmp->len);
for (i = 0; i < priv->n_chars; i++)
g_string_append_len (str, buf, char_len);
pango_attr_list_splice (tmp_attrs, priv->preedit_attrs,
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)
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);
}
g_free (contents);
return layout;
}
@ -505,6 +549,7 @@ clutter_text_position_to_coords (ClutterText *self,
{
ClutterTextPrivate *priv;
PangoRectangle rect;
gint n_chars;
gint password_char_bytes = 1;
gint index_;
@ -512,7 +557,12 @@ clutter_text_position_to_coords (ClutterText *self,
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;
if (priv->password_char != 0)
@ -523,7 +573,7 @@ clutter_text_position_to_coords (ClutterText *self,
if (priv->password_char == 0)
index_ = priv->n_bytes;
else
index_ = priv->n_chars * password_char_bytes;
index_ = n_chars * password_char_bytes;
}
else if (position == 0)
{
@ -534,10 +584,11 @@ clutter_text_position_to_coords (ClutterText *self,
if (priv->password_char == 0)
index_ = offset_to_bytes (priv->text, position);
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);
if (x)
@ -566,9 +617,17 @@ clutter_text_ensure_cursor_position (ClutterText *self)
ClutterGeometry cursor_pos = { 0, };
gboolean x_changed, y_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;
clutter_text_position_to_coords (self, priv->position,
clutter_text_position_to_coords (self, position,
&x, &y,
&cursor_height);
@ -1033,11 +1092,14 @@ cursor_paint (ClutterText *self)
{
guint8 paint_opacity = clutter_actor_get_paint_opacity (actor);
const ClutterColor *color;
gint position;
if (priv->position == 0)
position = priv->position;
if (position == 0)
priv->cursor_pos.x -= priv->cursor_size;
if (priv->position == priv->selection_bound)
if (position == priv->selection_bound)
{
if (priv->cursor_color_set)
color = &priv->cursor_color;
@ -1055,12 +1117,11 @@ cursor_paint (ClutterText *self)
priv->cursor_pos.y,
priv->cursor_pos.x + priv->cursor_pos.width,
priv->cursor_pos.y + priv->cursor_pos.height);
}
else
{
PangoLayout *layout = clutter_text_get_layout (self);
const gchar *utf8 = priv->text;
gchar *utf8 = clutter_text_get_display_text (self);
gint lines;
gint start_index;
gint end_index;
@ -1080,10 +1141,10 @@ cursor_paint (ClutterText *self)
* color->alpha
/ 255);
if (priv->position == 0)
if (position == 0)
start_index = 0;
else
start_index = offset_to_bytes (utf8, priv->position);
start_index = offset_to_bytes (utf8, position);
if (priv->selection_bound == 0)
end_index = 0;
@ -1146,6 +1207,8 @@ cursor_paint (ClutterText *self)
g_free (ranges);
}
g_free (utf8);
}
}
}
@ -1554,7 +1617,6 @@ clutter_text_paint (ClutterActor *self)
* scrolling */
priv->text_x = text_x;
clutter_text_ensure_cursor_position (text);
}
else
{
@ -2606,6 +2668,7 @@ clutter_text_init (ClutterText *self)
priv->selection_color_set = FALSE;
priv->cursor_color_set = FALSE;
priv->preedit_set = FALSE;
priv->password_char = 0;
@ -4383,3 +4446,69 @@ clutter_text_get_single_line_mode (ClutterText *self)
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 *line_height);
void clutter_text_set_preedit_string (ClutterText *self,
const gchar *preedit_str,
PangoAttrList *preedit_attr,
guint cursor_position);
G_END_DECLS
#endif /* __CLUTTER_TEXT_H__ */

View File

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

View File

@ -30,6 +30,182 @@ on_entry_activate (ClutterText *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 *
create_label (const ClutterColor *color,
const gchar *text)
@ -77,6 +253,9 @@ create_entry (const ClutterColor *color,
g_signal_connect (retval, "paint",
G_CALLBACK (on_entry_paint),
NULL);
g_signal_connect (retval, "captured-event",
G_CALLBACK (on_captured_event),
NULL);
return retval;
}