From 3a914a915ece9a8c4fc1175720b749c88aba3434 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 6 Dec 2017 12:49:34 +0100 Subject: [PATCH] clutter: Implement ClutterInputFocus subclass for ClutterText This only applies when the actor is editable. This object will allow editable ClutterText instances to interact with the input method. --- clutter/clutter/clutter-text.c | 179 ++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 2 deletions(-) diff --git a/clutter/clutter/clutter-text.c b/clutter/clutter/clutter-text.c index b57b3c535..49d7b9b97 100644 --- a/clutter/clutter/clutter-text.c +++ b/clutter/clutter/clutter-text.c @@ -63,6 +63,7 @@ #include "clutter-units.h" #include "clutter-paint-volume-private.h" #include "clutter-scriptable.h" +#include "clutter-input-focus.h" /* cursor width in pixels */ #define DEFAULT_CURSOR_SIZE 2 @@ -98,6 +99,12 @@ struct _LayoutCache guint age; }; +struct _ClutterTextInputFocus +{ + ClutterInputFocus parent_instance; + ClutterText *text; +}; + struct _ClutterTextPrivate { PangoFontDescription *font_desc; @@ -176,6 +183,8 @@ struct _ClutterTextPrivate /* Signal handler for when the :text-direction changes */ guint direction_changed_id; + ClutterInputFocus *input_focus; + /* bitfields */ guint alignment : 2; guint wrap : 1; @@ -269,6 +278,111 @@ static const ClutterColor default_selected_text_color = { 0, 0, 0, 255 }; static ClutterAnimatableIface *parent_animatable_iface = NULL; static ClutterScriptableIface *parent_scriptable_iface = NULL; +/* ClutterTextInputFocus */ +#define CLUTTER_TYPE_TEXT_INPUT_FOCUS (clutter_text_input_focus_get_type ()) + +G_DECLARE_FINAL_TYPE (ClutterTextInputFocus, clutter_text_input_focus, + CLUTTER, TEXT_INPUT_FOCUS, ClutterInputFocus) +G_DEFINE_TYPE (ClutterTextInputFocus, clutter_text_input_focus, + CLUTTER_TYPE_INPUT_FOCUS) + +static void +clutter_text_input_focus_request_surrounding (ClutterInputFocus *focus) +{ + ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; + ClutterTextBuffer *buffer; + const gchar *text; + gint anchor_pos, cursor_pos; + + buffer = clutter_text_get_buffer (clutter_text); + text = clutter_text_buffer_get_text (buffer); + + cursor_pos = clutter_text_get_cursor_position (clutter_text); + if (cursor_pos < 0) + cursor_pos = clutter_text_buffer_get_length (buffer); + + anchor_pos = clutter_text_get_selection_bound (clutter_text); + if (anchor_pos < 0) + anchor_pos = cursor_pos; + + clutter_input_focus_set_surrounding (focus, text, + g_utf8_offset_to_pointer (text, cursor_pos) - text, + g_utf8_offset_to_pointer (text, anchor_pos) - text); +} + +static void +clutter_text_input_focus_delete_surrounding (ClutterInputFocus *focus, + guint offset, + guint len) +{ + ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; + + if (clutter_text_get_editable (clutter_text)) + clutter_text_delete_text (clutter_text, offset, len); +} + +static void +clutter_text_input_focus_commit_text (ClutterInputFocus *focus, + const gchar *text) +{ + ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; + + if (clutter_text_get_editable (clutter_text)) + { + clutter_text_delete_selection (clutter_text); + clutter_text_insert_text (clutter_text, text, + clutter_text_get_cursor_position (clutter_text)); + } +} + +static void +clutter_text_input_focus_set_preedit_text (ClutterInputFocus *focus, + const gchar *preedit_text, + guint cursor_pos) +{ + ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; + + if (clutter_text_get_editable (clutter_text)) + { + PangoAttrList *list; + + list = pango_attr_list_new (); + pango_attr_list_insert (list, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE)); + clutter_text_set_preedit_string (clutter_text, + preedit_text, list, + cursor_pos); + pango_attr_list_unref (list); + } +} + +static void +clutter_text_input_focus_class_init (ClutterTextInputFocusClass *klass) +{ + ClutterInputFocusClass *focus_class = CLUTTER_INPUT_FOCUS_CLASS (klass); + + focus_class->request_surrounding = clutter_text_input_focus_request_surrounding; + focus_class->delete_surrounding = clutter_text_input_focus_delete_surrounding; + focus_class->commit_text = clutter_text_input_focus_commit_text; + focus_class->set_preedit_text = clutter_text_input_focus_set_preedit_text; +} + +static void +clutter_text_input_focus_init (ClutterTextInputFocus *focus) +{ +} + +static ClutterInputFocus * +clutter_text_input_focus_new (ClutterText *text) +{ + ClutterTextInputFocus *focus; + + focus = g_object_new (CLUTTER_TYPE_TEXT_INPUT_FOCUS, NULL); + focus->text = text; + + return CLUTTER_INPUT_FOCUS (focus); +} + +/* ClutterText */ static void clutter_scriptable_iface_init (ClutterScriptableIface *iface); static void clutter_animatable_iface_init (ClutterAnimatableIface *iface); @@ -1009,6 +1123,22 @@ clutter_text_position_to_coords (ClutterText *self, return TRUE; } +static inline void +update_cursor_location (ClutterText *self) +{ + ClutterTextPrivate *priv = self->priv; + ClutterRect rect; + float x, y; + + if (!priv->editable) + return; + + rect = priv->cursor_rect; + clutter_actor_get_transformed_position (CLUTTER_ACTOR (self), &x, &y); + clutter_rect_offset (&rect, x, y); + clutter_input_focus_set_cursor_location (priv->input_focus, &rect); +} + static inline void clutter_text_ensure_cursor_position (ClutterText *self) { @@ -1057,6 +1187,8 @@ clutter_text_ensure_cursor_position (ClutterText *self) g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &cursor_pos); g_signal_emit (self, text_signals[CURSOR_CHANGED], 0); + + update_cursor_location (self); } } @@ -1503,6 +1635,8 @@ clutter_text_finalize (GObject *gobject) clutter_text_set_buffer (self, NULL); g_free (priv->font_name); + g_clear_object (&priv->input_focus); + G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject); } @@ -2085,9 +2219,11 @@ clutter_text_key_press (ClutterActor *actor, g_assert (pool != NULL); /* we allow passing synthetic events that only contain - * the Unicode value and not the key symbol + * the Unicode value and not the key symbol, unless they + * contain the input method flag. */ - if (event->keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC)) + if (event->keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC) && + !(event->flags & CLUTTER_EVENT_FLAG_INPUT_METHOD)) res = FALSE; else res = clutter_binding_pool_activate (pool, event->keyval, @@ -2105,6 +2241,9 @@ clutter_text_key_press (ClutterActor *actor, { gunichar key_unichar; + if (clutter_input_focus_filter_key_event (priv->input_focus, event)) + return CLUTTER_EVENT_STOP; + /* Skip keys when control is pressed */ key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) event); @@ -2141,6 +2280,19 @@ clutter_text_key_press (ClutterActor *actor, return CLUTTER_EVENT_PROPAGATE; } +static gboolean +clutter_text_key_release (ClutterActor *actor, + ClutterKeyEvent *event) +{ + ClutterText *self = CLUTTER_TEXT (actor); + ClutterTextPrivate *priv = self->priv; + + if (clutter_input_focus_filter_key_event (priv->input_focus, event)) + return CLUTTER_EVENT_STOP; + + return CLUTTER_EVENT_PROPAGATE; +} + static void clutter_text_compute_layout_offsets (ClutterText *self, PangoLayout *layout, @@ -2663,6 +2815,11 @@ static void clutter_text_key_focus_in (ClutterActor *actor) { ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv; + ClutterBackend *backend = clutter_get_default_backend (); + ClutterInputMethod *method = clutter_backend_get_input_method (backend); + + if (method && priv->editable) + clutter_input_method_focus_in (method, priv->input_focus); priv->has_focus = TRUE; @@ -2673,9 +2830,14 @@ static void clutter_text_key_focus_out (ClutterActor *actor) { ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv; + ClutterBackend *backend = clutter_get_default_backend (); + ClutterInputMethod *method = clutter_backend_get_input_method (backend); priv->has_focus = FALSE; + if (priv->editable && clutter_input_focus_is_focused (priv->input_focus)) + clutter_input_method_focus_out (method); + clutter_text_queue_redraw (actor); } @@ -3369,6 +3531,7 @@ clutter_text_class_init (ClutterTextClass *klass) actor_class->get_preferred_height = clutter_text_get_preferred_height; actor_class->allocate = clutter_text_allocate; actor_class->key_press_event = clutter_text_key_press; + actor_class->key_release_event = clutter_text_key_release; actor_class->button_press_event = clutter_text_button_press; actor_class->button_release_event = clutter_text_button_release; actor_class->motion_event = clutter_text_motion; @@ -4169,6 +4332,8 @@ clutter_text_init (ClutterText *self) g_signal_connect (self, "notify::text-direction", G_CALLBACK (clutter_text_direction_changed_cb), NULL); + + priv->input_focus = clutter_text_input_focus_new (self); } /** @@ -4459,6 +4624,8 @@ void clutter_text_set_editable (ClutterText *self, gboolean editable) { + ClutterBackend *backend = clutter_get_default_backend (); + ClutterInputMethod *method = clutter_backend_get_input_method (backend); ClutterTextPrivate *priv; g_return_if_fail (CLUTTER_IS_TEXT (self)); @@ -4469,6 +4636,14 @@ clutter_text_set_editable (ClutterText *self, { priv->editable = editable; + if (method) + { + if (!priv->editable && clutter_input_focus_is_focused (priv->input_focus)) + clutter_input_method_focus_out (method); + else if (priv->has_focus) + clutter_input_method_focus_in (method, priv->input_focus); + } + clutter_text_queue_redraw (CLUTTER_ACTOR (self)); g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_EDITABLE]);